summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/AjaxDispatcher.php9
-rw-r--r--includes/AutoLoader.php1194
-rw-r--r--includes/Autopromote.php4
-rw-r--r--includes/Block.php66
-rw-r--r--includes/Category.php11
-rw-r--r--includes/CategoryFinder.php1
-rw-r--r--includes/CategoryViewer.php119
-rw-r--r--includes/CdbCompat.php45
-rw-r--r--includes/ChangeTags.php931
-rw-r--r--includes/Collation.php4
-rw-r--r--includes/DefaultSettings.php502
-rw-r--r--includes/Defines.php29
-rw-r--r--includes/EditPage.php469
-rw-r--r--includes/Export.php37
-rw-r--r--includes/Feed.php14
-rw-r--r--includes/FeedUtils.php7
-rw-r--r--includes/FileDeleteForm.php4
-rw-r--r--includes/GitInfo.php2
-rw-r--r--includes/GlobalFunctions.php723
-rw-r--r--includes/Hooks.php20
-rw-r--r--includes/Html.php192
-rw-r--r--includes/HtmlFormatter.php18
-rw-r--r--includes/HttpFunctions.php98
-rw-r--r--includes/Import.php377
-rw-r--r--includes/LinkFilter.php2
-rw-r--r--includes/Linker.php172
-rw-r--r--includes/MWNamespace.php4
-rw-r--r--includes/MWTimestamp.php9
-rw-r--r--includes/MagicWord.php12
-rw-r--r--includes/MediaWiki.php116
-rw-r--r--includes/MediaWikiVersionFetcher.php3
-rw-r--r--includes/Message.php79
-rw-r--r--includes/MessageBlobStore.php14
-rw-r--r--includes/MimeMagic.php14
-rw-r--r--includes/MovePage.php204
-rw-r--r--includes/NoLocalSettings.php (renamed from includes/templates/NoLocalSettings.php)70
-rw-r--r--includes/OutputHandler.php3
-rw-r--r--includes/OutputPage.php266
-rw-r--r--includes/PHPVersionCheck.php157
-rw-r--r--includes/PHPVersionError.php110
-rw-r--r--includes/Preferences.php102
-rw-r--r--includes/PrefixSearch.php161
-rw-r--r--includes/ProtectionForm.php69
-rw-r--r--includes/Revision.php125
-rw-r--r--includes/RevisionList.php27
-rw-r--r--includes/Sanitizer.php109
-rw-r--r--includes/Setup.php100
-rw-r--r--includes/SiteConfiguration.php4
-rw-r--r--includes/SiteStats.php37
-rw-r--r--includes/StatCounter.php154
-rw-r--r--includes/Status.php252
-rw-r--r--includes/StreamFile.php5
-rw-r--r--includes/StubObject.php27
-rw-r--r--includes/TemplateParser.php201
-rw-r--r--includes/Title.php526
-rw-r--r--includes/TitleArray.php2
-rw-r--r--includes/User.php636
-rw-r--r--includes/UserArray.php23
-rw-r--r--includes/UserRightsProxy.php11
-rw-r--r--includes/WatchedItem.php10
-rw-r--r--includes/WebRequest.php66
-rw-r--r--includes/WebResponse.php42
-rw-r--r--includes/WebStart.php37
-rw-r--r--includes/WikiMap.php2
-rw-r--r--includes/Xml.php12
-rw-r--r--includes/ZhConversion.php10864
-rw-r--r--includes/actions/Action.php24
-rw-r--r--includes/actions/CreditsAction.php6
-rw-r--r--includes/actions/DeleteAction.php3
-rw-r--r--includes/actions/EditAction.php2
-rw-r--r--includes/actions/FormAction.php4
-rw-r--r--includes/actions/HistoryAction.php59
-rw-r--r--includes/actions/InfoAction.php53
-rw-r--r--includes/actions/RawAction.php2
-rw-r--r--includes/actions/RevertAction.php4
-rw-r--r--includes/actions/RevisiondeleteAction.php6
-rw-r--r--includes/actions/SpecialPageAction.php79
-rw-r--r--includes/actions/UnwatchAction.php2
-rw-r--r--includes/actions/WatchAction.php10
-rw-r--r--includes/api/ApiBase.php1084
-rw-r--r--includes/api/ApiBlock.php62
-rw-r--r--includes/api/ApiCheckToken.php81
-rw-r--r--includes/api/ApiClearHasMsg.php9
-rw-r--r--includes/api/ApiComparePages.php25
-rw-r--r--includes/api/ApiContinuationManager.php238
-rw-r--r--includes/api/ApiCreateAccount.php48
-rw-r--r--includes/api/ApiDelete.php29
-rw-r--r--includes/api/ApiDisabled.php16
-rw-r--r--includes/api/ApiEditPage.php143
-rw-r--r--includes/api/ApiEmailUser.php19
-rw-r--r--includes/api/ApiErrorFormatter.php303
-rw-r--r--includes/api/ApiExpandTemplates.php85
-rw-r--r--includes/api/ApiFeedContributions.php54
-rw-r--r--includes/api/ApiFeedRecentChanges.php35
-rw-r--r--includes/api/ApiFeedWatchlist.php90
-rw-r--r--includes/api/ApiFileRevert.php22
-rw-r--r--includes/api/ApiFormatBase.php332
-rw-r--r--includes/api/ApiFormatDbg.php11
-rw-r--r--includes/api/ApiFormatDump.php11
-rw-r--r--includes/api/ApiFormatFeedWrapper.php30
-rw-r--r--includes/api/ApiFormatJson.php96
-rw-r--r--includes/api/ApiFormatNone.php4
-rw-r--r--includes/api/ApiFormatPhp.php35
-rw-r--r--includes/api/ApiFormatRaw.php31
-rw-r--r--includes/api/ApiFormatTxt.php11
-rw-r--r--includes/api/ApiFormatWddx.php73
-rw-r--r--includes/api/ApiFormatXml.php302
-rw-r--r--includes/api/ApiFormatYaml.php4
-rw-r--r--includes/api/ApiHelp.php675
-rw-r--r--includes/api/ApiHelpParamValueMessage.php72
-rw-r--r--includes/api/ApiImageRotate.php42
-rw-r--r--includes/api/ApiImport.php35
-rw-r--r--includes/api/ApiLogin.php35
-rw-r--r--includes/api/ApiLogout.php19
-rw-r--r--includes/api/ApiMain.php541
-rw-r--r--includes/api/ApiManageTags.php107
-rw-r--r--includes/api/ApiMessage.php191
-rw-r--r--includes/api/ApiMove.php97
-rw-r--r--includes/api/ApiOpenSearch.php349
-rw-r--r--includes/api/ApiOptions.php37
-rw-r--r--includes/api/ApiPageSet.php337
-rw-r--r--includes/api/ApiParamInfo.php455
-rw-r--r--includes/api/ApiParse.php294
-rw-r--r--includes/api/ApiPatrol.php21
-rw-r--r--includes/api/ApiProtect.php46
-rw-r--r--includes/api/ApiPurge.php39
-rw-r--r--includes/api/ApiQuery.php117
-rw-r--r--includes/api/ApiQueryAllCategories.php42
-rw-r--r--includes/api/ApiQueryAllDeletedRevisions.php427
-rw-r--r--includes/api/ApiQueryAllImages.php99
-rw-r--r--includes/api/ApiQueryAllLinks.php95
-rw-r--r--includes/api/ApiQueryAllMessages.php43
-rw-r--r--includes/api/ApiQueryAllPages.php79
-rw-r--r--includes/api/ApiQueryAllUsers.php273
-rw-r--r--includes/api/ApiQueryBacklinks.php483
-rw-r--r--includes/api/ApiQueryBacklinksprop.php114
-rw-r--r--includes/api/ApiQueryBase.php42
-rw-r--r--includes/api/ApiQueryBlocks.php114
-rw-r--r--includes/api/ApiQueryCategories.php42
-rw-r--r--includes/api/ApiQueryCategoryInfo.php26
-rw-r--r--includes/api/ApiQueryCategoryMembers.php89
-rw-r--r--includes/api/ApiQueryContributors.php37
-rw-r--r--includes/api/ApiQueryDeletedRevisions.php304
-rw-r--r--includes/api/ApiQueryDeletedrevs.php158
-rw-r--r--includes/api/ApiQueryDisabled.php14
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php33
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php60
-rw-r--r--includes/api/ApiQueryExternalLinks.php31
-rw-r--r--includes/api/ApiQueryFileRepoInfo.php27
-rw-r--r--includes/api/ApiQueryFilearchive.php69
-rw-r--r--includes/api/ApiQueryIWBacklinks.php39
-rw-r--r--includes/api/ApiQueryIWLinks.php51
-rw-r--r--includes/api/ApiQueryImageInfo.php121
-rw-r--r--includes/api/ApiQueryImages.php28
-rw-r--r--includes/api/ApiQueryInfo.php122
-rw-r--r--includes/api/ApiQueryLangBacklinks.php40
-rw-r--r--includes/api/ApiQueryLangLinks.php55
-rw-r--r--includes/api/ApiQueryLinks.php43
-rw-r--r--includes/api/ApiQueryLogEvents.php184
-rw-r--r--includes/api/ApiQueryORM.php11
-rw-r--r--includes/api/ApiQueryPagePropNames.php22
-rw-r--r--includes/api/ApiQueryPageProps.php22
-rw-r--r--includes/api/ApiQueryPagesWithProp.php39
-rw-r--r--includes/api/ApiQueryPrefixSearch.php40
-rw-r--r--includes/api/ApiQueryProtectedTitles.php40
-rw-r--r--includes/api/ApiQueryQueryPage.php28
-rw-r--r--includes/api/ApiQueryRandom.php27
-rw-r--r--includes/api/ApiQueryRecentChanges.php122
-rw-r--r--includes/api/ApiQueryRevisions.php616
-rw-r--r--includes/api/ApiQueryRevisionsBase.php477
-rw-r--r--includes/api/ApiQuerySearch.php160
-rw-r--r--includes/api/ApiQuerySiteinfo.php260
-rw-r--r--includes/api/ApiQueryStashImageInfo.php52
-rw-r--r--includes/api/ApiQueryTags.php169
-rw-r--r--includes/api/ApiQueryTokens.php30
-rw-r--r--includes/api/ApiQueryUserContributions.php92
-rw-r--r--includes/api/ApiQueryUserInfo.php89
-rw-r--r--includes/api/ApiQueryUsers.php69
-rw-r--r--includes/api/ApiQueryWatchlist.php122
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php36
-rw-r--r--includes/api/ApiResult.php1553
-rw-r--r--includes/api/ApiRevisionDelete.php36
-rw-r--r--includes/api/ApiRollback.php36
-rw-r--r--includes/api/ApiRsd.php44
-rw-r--r--includes/api/ApiSerializable.php47
-rw-r--r--includes/api/ApiSetNotificationTimestamp.php67
-rw-r--r--includes/api/ApiStashEdit.php412
-rw-r--r--includes/api/ApiTag.php177
-rw-r--r--includes/api/ApiTokens.php39
-rw-r--r--includes/api/ApiUnblock.php24
-rw-r--r--includes/api/ApiUndelete.php36
-rw-r--r--includes/api/ApiUpload.php138
-rw-r--r--includes/api/ApiUserrights.php56
-rw-r--r--includes/api/ApiWatch.php58
-rw-r--r--includes/api/i18n/ar.json28
-rw-r--r--includes/api/i18n/av.json8
-rw-r--r--includes/api/i18n/awa.json11
-rw-r--r--includes/api/i18n/be-tarask.json55
-rw-r--r--includes/api/i18n/bn.json8
-rw-r--r--includes/api/i18n/bs.json16
-rw-r--r--includes/api/i18n/ca.json43
-rw-r--r--includes/api/i18n/ce.json12
-rw-r--r--includes/api/i18n/cs.json223
-rw-r--r--includes/api/i18n/cv.json8
-rw-r--r--includes/api/i18n/de.json433
-rw-r--r--includes/api/i18n/el.json12
-rw-r--r--includes/api/i18n/en-gb.json156
-rw-r--r--includes/api/i18n/en.json1169
-rw-r--r--includes/api/i18n/es.json206
-rw-r--r--includes/api/i18n/eu.json57
-rw-r--r--includes/api/i18n/fa.json240
-rw-r--r--includes/api/i18n/fi.json10
-rw-r--r--includes/api/i18n/fr.json1054
-rw-r--r--includes/api/i18n/frc.json19
-rw-r--r--includes/api/i18n/fy.json15
-rw-r--r--includes/api/i18n/gl.json1030
-rw-r--r--includes/api/i18n/he.json180
-rw-r--r--includes/api/i18n/hsb.json8
-rw-r--r--includes/api/i18n/hu.json17
-rw-r--r--includes/api/i18n/ia.json28
-rw-r--r--includes/api/i18n/it.json31
-rw-r--r--includes/api/i18n/ja.json213
-rw-r--r--includes/api/i18n/jam.json8
-rw-r--r--includes/api/i18n/ko.json36
-rw-r--r--includes/api/i18n/ksh.json311
-rw-r--r--includes/api/i18n/ku-latn.json9
-rw-r--r--includes/api/i18n/lb.json94
-rw-r--r--includes/api/i18n/ln.json8
-rw-r--r--includes/api/i18n/lv.json8
-rw-r--r--includes/api/i18n/lzh.json8
-rw-r--r--includes/api/i18n/mg.json11
-rw-r--r--includes/api/i18n/mk.json398
-rw-r--r--includes/api/i18n/ms.json61
-rw-r--r--includes/api/i18n/nap.json10
-rw-r--r--includes/api/i18n/nb.json24
-rw-r--r--includes/api/i18n/nds.json8
-rw-r--r--includes/api/i18n/nl.json61
-rw-r--r--includes/api/i18n/oc.json17
-rw-r--r--includes/api/i18n/pa.json8
-rw-r--r--includes/api/i18n/pam.json31
-rw-r--r--includes/api/i18n/pl.json117
-rw-r--r--includes/api/i18n/ps.json29
-rw-r--r--includes/api/i18n/pt-br.json14
-rw-r--r--includes/api/i18n/pt.json104
-rw-r--r--includes/api/i18n/qqq.json1069
-rw-r--r--includes/api/i18n/roa-tara.json11
-rw-r--r--includes/api/i18n/ru.json55
-rw-r--r--includes/api/i18n/si.json70
-rw-r--r--includes/api/i18n/sr-ec.json24
-rw-r--r--includes/api/i18n/sr-el.json11
-rw-r--r--includes/api/i18n/sv.json372
-rw-r--r--includes/api/i18n/te.json8
-rw-r--r--includes/api/i18n/tl.json35
-rw-r--r--includes/api/i18n/tr.json40
-rw-r--r--includes/api/i18n/uk.json30
-rw-r--r--includes/api/i18n/vi.json156
-rw-r--r--includes/api/i18n/zh-hans.json782
-rw-r--r--includes/api/i18n/zh-hant.json244
-rw-r--r--includes/cache/BacklinkCache.php60
-rw-r--r--includes/cache/CacheDependency.php18
-rw-r--r--includes/cache/HTMLFileCache.php2
-rw-r--r--includes/cache/LinkBatch.php4
-rw-r--r--includes/cache/LinkCache.php17
-rw-r--r--includes/cache/LocalisationCache.php71
-rw-r--r--includes/cache/MessageCache.php36
-rw-r--r--includes/cache/ResourceFileCache.php9
-rw-r--r--includes/cache/UserCache.php2
-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.php4
-rw-r--r--includes/changes/ChangesList.php6
-rw-r--r--includes/changes/EnhancedChangesList.php213
-rw-r--r--includes/changes/OldChangesList.php9
-rw-r--r--includes/changes/RecentChange.php39
-rw-r--r--includes/changetags/ChangeTagsList.php77
-rw-r--r--includes/changetags/ChangeTagsLogItem.php100
-rw-r--r--includes/changetags/ChangeTagsLogList.php89
-rw-r--r--includes/changetags/ChangeTagsRevisionItem.php58
-rw-r--r--includes/changetags/ChangeTagsRevisionList.php99
-rw-r--r--includes/config/ConfigException.php2
-rw-r--r--includes/config/GlobalVarConfig.php21
-rw-r--r--includes/content/AbstractContent.php27
-rw-r--r--includes/content/CodeContentHandler.php1
-rw-r--r--includes/content/Content.php7
-rw-r--r--includes/content/ContentHandler.php23
-rw-r--r--includes/content/JavaScriptContentHandler.php8
-rw-r--r--includes/content/JsonContent.php192
-rw-r--r--includes/content/JsonContentHandler.php24
-rw-r--r--includes/content/TextContent.php1
-rw-r--r--includes/content/WikitextContent.php7
-rw-r--r--includes/context/ContextSource.php17
-rw-r--r--includes/context/DerivativeContext.php18
-rw-r--r--includes/context/IContextSource.php38
-rw-r--r--includes/context/RequestContext.php88
-rw-r--r--includes/db/Database.php856
-rw-r--r--includes/db/DatabaseError.php6
-rw-r--r--includes/db/DatabaseMssql.php86
-rw-r--r--includes/db/DatabaseMysql.php7
-rw-r--r--includes/db/DatabaseMysqlBase.php125
-rw-r--r--includes/db/DatabaseMysqli.php14
-rw-r--r--includes/db/DatabaseOracle.php18
-rw-r--r--includes/db/DatabasePostgres.php23
-rw-r--r--includes/db/DatabaseSqlite.php163
-rw-r--r--includes/db/DatabaseUtility.php27
-rw-r--r--includes/db/IORMTable.php18
-rw-r--r--includes/db/LBFactory.php78
-rw-r--r--includes/db/LBFactoryMulti.php58
-rw-r--r--includes/db/LBFactorySingle.php20
-rw-r--r--includes/db/LoadBalancer.php390
-rw-r--r--includes/db/LoadMonitor.php4
-rw-r--r--includes/db/ORMTable.php50
-rw-r--r--includes/debug/MWDebug.php33
-rw-r--r--includes/debug/logger/LegacyLogger.php380
-rw-r--r--includes/debug/logger/LegacySpi.php59
-rw-r--r--includes/debug/logger/LoggerFactory.php121
-rw-r--r--includes/debug/logger/MonologSpi.php251
-rw-r--r--includes/debug/logger/NullSpi.php64
-rw-r--r--includes/debug/logger/Spi.php47
-rw-r--r--includes/debug/logger/monolog/LegacyFormatter.php48
-rw-r--r--includes/debug/logger/monolog/LegacyHandler.php243
-rw-r--r--includes/debug/logger/monolog/SyslogHandler.php96
-rw-r--r--includes/debug/logger/monolog/WikiProcessor.php47
-rw-r--r--includes/deferred/DeferredUpdates.php6
-rw-r--r--includes/deferred/HTMLCacheUpdate.php5
-rw-r--r--includes/deferred/LinksUpdate.php33
-rw-r--r--includes/deferred/SearchUpdate.php7
-rw-r--r--includes/deferred/SiteStatsUpdate.php9
-rw-r--r--includes/deferred/SqlDataUpdate.php85
-rw-r--r--includes/deferred/SquidUpdate.php43
-rw-r--r--includes/deferred/ViewCountUpdate.php119
-rw-r--r--includes/diff/DairikiDiff.php18
-rw-r--r--includes/diff/DiffFormatter.php4
-rw-r--r--includes/diff/DifferenceEngine.php61
-rw-r--r--includes/diff/TableDiffFormatter.php4
-rw-r--r--includes/exception/HttpError.php42
-rw-r--r--includes/exception/MWException.php23
-rw-r--r--includes/exception/MWExceptionHandler.php201
-rw-r--r--includes/exception/TimestampException.php (renamed from includes/TimestampException.php)0
-rw-r--r--includes/exception/UserNotLoggedIn.php10
-rw-r--r--includes/externalstore/ExternalStore.php2
-rw-r--r--includes/externalstore/ExternalStoreHttp.php2
-rw-r--r--includes/filebackend/FSFile.php7
-rw-r--r--includes/filebackend/FSFileBackend.php8
-rw-r--r--includes/filebackend/FileBackend.php4
-rw-r--r--includes/filebackend/FileBackendMultiWrite.php6
-rw-r--r--includes/filebackend/FileBackendStore.php87
-rw-r--r--includes/filebackend/FileOpBatch.php1
-rw-r--r--includes/filebackend/SwiftFileBackend.php81
-rw-r--r--includes/filebackend/TempFSFile.php3
-rw-r--r--includes/filebackend/filejournal/FileJournal.php4
-rw-r--r--includes/filebackend/lockmanager/DBLockManager.php4
-rw-r--r--includes/filebackend/lockmanager/LockManager.php10
-rw-r--r--includes/filebackend/lockmanager/LockManagerGroup.php16
-rw-r--r--includes/filebackend/lockmanager/MemcLockManager.php6
-rw-r--r--includes/filebackend/lockmanager/RedisLockManager.php2
-rw-r--r--includes/filerepo/FileRepo.php64
-rw-r--r--includes/filerepo/FileRepoStatus.php36
-rw-r--r--includes/filerepo/ForeignAPIRepo.php2
-rw-r--r--includes/filerepo/RepoGroup.php6
-rw-r--r--includes/filerepo/file/ArchivedFile.php12
-rw-r--r--includes/filerepo/file/File.php51
-rw-r--r--includes/filerepo/file/LocalFile.php194
-rw-r--r--includes/filerepo/file/OldLocalFile.php14
-rw-r--r--includes/filerepo/file/UnregisteredLocalFile.php12
-rw-r--r--includes/gallery/ImageGalleryBase.php20
-rw-r--r--includes/gallery/TraditionalImageGallery.php4
-rw-r--r--includes/htmlform/HTMLCheckField.php46
-rw-r--r--includes/htmlform/HTMLCheckMatrix.php14
-rw-r--r--includes/htmlform/HTMLFloatField.php2
-rw-r--r--includes/htmlform/HTMLForm.php191
-rw-r--r--includes/htmlform/HTMLFormField.php50
-rw-r--r--includes/htmlform/HTMLFormFieldCloner.php42
-rw-r--r--includes/htmlform/HTMLHiddenField.php18
-rw-r--r--includes/htmlform/HTMLIntField.php5
-rw-r--r--includes/htmlform/HTMLMultiSelectField.php8
-rw-r--r--includes/htmlform/HTMLRadioField.php2
-rw-r--r--includes/htmlform/HTMLSelectAndOtherField.php11
-rw-r--r--includes/htmlform/HTMLSelectNamespace.php18
-rw-r--r--includes/htmlform/HTMLTagFilter.php31
-rw-r--r--includes/htmlform/HTMLTextField.php7
-rw-r--r--includes/htmlform/VFormHTMLForm.php141
-rw-r--r--includes/installer/DatabaseInstaller.php15
-rw-r--r--includes/installer/DatabaseUpdater.php60
-rw-r--r--includes/installer/InstallDocFormatter.php34
-rw-r--r--includes/installer/Installer.php87
-rw-r--r--includes/installer/LocalSettingsGenerator.php39
-rw-r--r--includes/installer/MssqlInstaller.php4
-rw-r--r--includes/installer/MssqlUpdater.php5
-rw-r--r--includes/installer/MysqlInstaller.php9
-rw-r--r--includes/installer/MysqlUpdater.php45
-rw-r--r--includes/installer/OracleUpdater.php6
-rw-r--r--includes/installer/PostgresInstaller.php16
-rw-r--r--includes/installer/PostgresUpdater.php6
-rw-r--r--includes/installer/SqliteInstaller.php72
-rw-r--r--includes/installer/SqliteUpdater.php13
-rw-r--r--includes/installer/WebInstaller.php27
-rw-r--r--includes/installer/WebInstallerOutput.php37
-rw-r--r--includes/installer/WebInstallerPage.php21
-rw-r--r--includes/installer/i18n/ar.json8
-rw-r--r--includes/installer/i18n/ast.json9
-rw-r--r--includes/installer/i18n/av.json5
-rw-r--r--includes/installer/i18n/az.json2
-rw-r--r--includes/installer/i18n/ba.json21
-rw-r--r--includes/installer/i18n/be-tarask.json2
-rw-r--r--includes/installer/i18n/bg.json11
-rw-r--r--includes/installer/i18n/bgn.json26
-rw-r--r--includes/installer/i18n/bn.json13
-rw-r--r--includes/installer/i18n/bs.json33
-rw-r--r--includes/installer/i18n/ca.json49
-rw-r--r--includes/installer/i18n/ckb.json19
-rw-r--r--includes/installer/i18n/cs.json2
-rw-r--r--includes/installer/i18n/cv.json10
-rw-r--r--includes/installer/i18n/cy.json3
-rw-r--r--includes/installer/i18n/de.json24
-rw-r--r--includes/installer/i18n/diq.json2
-rw-r--r--includes/installer/i18n/el.json12
-rw-r--r--includes/installer/i18n/eml.json16
-rw-r--r--includes/installer/i18n/en-gb.json6
-rw-r--r--includes/installer/i18n/en.json9
-rw-r--r--includes/installer/i18n/es.json142
-rw-r--r--includes/installer/i18n/et.json16
-rw-r--r--includes/installer/i18n/eu.json5
-rw-r--r--includes/installer/i18n/fa.json40
-rw-r--r--includes/installer/i18n/fi.json51
-rw-r--r--includes/installer/i18n/fr.json5
-rw-r--r--includes/installer/i18n/frc.json21
-rw-r--r--includes/installer/i18n/fy.json14
-rw-r--r--includes/installer/i18n/gl.json2
-rw-r--r--includes/installer/i18n/gor.json48
-rw-r--r--includes/installer/i18n/he.json2
-rw-r--r--includes/installer/i18n/hi.json16
-rw-r--r--includes/installer/i18n/hu.json13
-rw-r--r--includes/installer/i18n/ia.json13
-rw-r--r--includes/installer/i18n/id.json6
-rw-r--r--includes/installer/i18n/it.json7
-rw-r--r--includes/installer/i18n/ja.json1
-rw-r--r--includes/installer/i18n/ko.json76
-rw-r--r--includes/installer/i18n/ksh.json31
-rw-r--r--includes/installer/i18n/ku-latn.json1
-rw-r--r--includes/installer/i18n/lb.json3
-rw-r--r--includes/installer/i18n/lzh.json1
-rw-r--r--includes/installer/i18n/mai.json26
-rw-r--r--includes/installer/i18n/mfe.json45
-rw-r--r--includes/installer/i18n/mk.json7
-rw-r--r--includes/installer/i18n/ms.json11
-rw-r--r--includes/installer/i18n/nap.json196
-rw-r--r--includes/installer/i18n/nb.json26
-rw-r--r--includes/installer/i18n/ne.json6
-rw-r--r--includes/installer/i18n/nl.json28
-rw-r--r--includes/installer/i18n/oc.json3
-rw-r--r--includes/installer/i18n/pa.json24
-rw-r--r--includes/installer/i18n/pl.json7
-rw-r--r--includes/installer/i18n/pms.json10
-rw-r--r--includes/installer/i18n/pt-br.json8
-rw-r--r--includes/installer/i18n/pt.json2
-rw-r--r--includes/installer/i18n/qqq.json3
-rw-r--r--includes/installer/i18n/ro.json22
-rw-r--r--includes/installer/i18n/roa-tara.json15
-rw-r--r--includes/installer/i18n/ru.json9
-rw-r--r--includes/installer/i18n/sco.json13
-rw-r--r--includes/installer/i18n/si.json4
-rw-r--r--includes/installer/i18n/sk.json5
-rw-r--r--includes/installer/i18n/sl.json16
-rw-r--r--includes/installer/i18n/sr-ec.json17
-rw-r--r--includes/installer/i18n/sr-el.json1
-rw-r--r--includes/installer/i18n/su.json8
-rw-r--r--includes/installer/i18n/sv.json8
-rw-r--r--includes/installer/i18n/te.json2
-rw-r--r--includes/installer/i18n/th.json90
-rw-r--r--includes/installer/i18n/tl.json2
-rw-r--r--includes/installer/i18n/tr.json7
-rw-r--r--includes/installer/i18n/uk.json2
-rw-r--r--includes/installer/i18n/vi.json137
-rw-r--r--includes/installer/i18n/yi.json5
-rw-r--r--includes/installer/i18n/zh-hans.json11
-rw-r--r--includes/installer/i18n/zh-hant.json23
-rw-r--r--includes/interwiki/Interwiki.php4
-rw-r--r--includes/jobqueue/Job.php120
-rw-r--r--includes/jobqueue/JobQueue.php83
-rw-r--r--includes/jobqueue/JobQueueDB.php6
-rw-r--r--includes/jobqueue/JobQueueFederated.php131
-rw-r--r--includes/jobqueue/JobQueueGroup.php14
-rw-r--r--includes/jobqueue/JobQueueRedis.php255
-rw-r--r--includes/jobqueue/JobRunner.php86
-rw-r--r--includes/jobqueue/JobSpecification.php36
-rw-r--r--includes/jobqueue/aggregator/JobQueueAggregator.php28
-rw-r--r--includes/jobqueue/aggregator/JobQueueAggregatorMemc.php125
-rw-r--r--includes/jobqueue/aggregator/JobQueueAggregatorRedis.php2
-rw-r--r--includes/jobqueue/jobs/AssembleUploadChunksJob.php19
-rw-r--r--includes/jobqueue/jobs/DuplicateJob.php2
-rw-r--r--includes/jobqueue/jobs/EnqueueJob.php88
-rw-r--r--includes/jobqueue/jobs/HTMLCacheUpdateJob.php46
-rw-r--r--includes/jobqueue/jobs/NullJob.php2
-rw-r--r--includes/jobqueue/jobs/PublishStashedFileJob.php22
-rw-r--r--includes/jobqueue/jobs/RecentChangesUpdateJob.php223
-rw-r--r--includes/jobqueue/jobs/RefreshLinksJob.php16
-rw-r--r--includes/jobqueue/jobs/RefreshLinksJob2.php141
-rw-r--r--includes/jobqueue/jobs/ThumbnailRenderJob.php109
-rw-r--r--includes/jobqueue/jobs/UploadFromUrlJob.php2
-rw-r--r--includes/json/FormatJson.php102
-rw-r--r--includes/libs/APACHE-LICENSE-2.0.txt202
-rw-r--r--includes/libs/ArrayUtils.php (renamed from includes/utils/ArrayUtils.php)4
-rw-r--r--includes/libs/BufferingStatsdDataFactory.php59
-rw-r--r--includes/libs/CSSJanus.php458
-rw-r--r--includes/libs/CSSMin.php151
-rw-r--r--includes/libs/Cookie.php (renamed from includes/Cookie.php)4
-rw-r--r--includes/libs/DeferredStringifier.php57
-rw-r--r--includes/libs/ExplodeIterator.php116
-rw-r--r--includes/libs/GenericArrayObject.php6
-rw-r--r--includes/libs/IPSet.php3
-rw-r--r--includes/libs/MapCacheLRU.php (renamed from includes/cache/MapCacheLRU.php)12
-rw-r--r--includes/libs/MessageSpecifier.php39
-rw-r--r--includes/libs/MultiHttpClient.php28
-rw-r--r--includes/libs/ObjectFactory.php93
-rw-r--r--includes/libs/ProcessCacheLRU.php39
-rw-r--r--includes/libs/ReplacementArray.php125
-rw-r--r--includes/libs/RunningStat.php8
-rw-r--r--includes/libs/ScopedCallback.php12
-rw-r--r--includes/libs/StatusValue.php316
-rw-r--r--includes/libs/StringUtils.php (renamed from includes/utils/StringUtils.php)301
-rw-r--r--includes/libs/UDPTransport.php102
-rw-r--r--includes/libs/Xhprof.php445
-rw-r--r--includes/libs/XmlTypeCheck.php2
-rw-r--r--includes/libs/composer/ComposerJson.php54
-rw-r--r--includes/libs/composer/ComposerLock.php38
-rw-r--r--includes/libs/jsminplus.php2
-rw-r--r--includes/libs/lessc.inc.php3796
-rw-r--r--includes/libs/normal/UtfNormal.php129
-rw-r--r--includes/libs/normal/UtfNormalDefines.php (renamed from includes/normal/UtfNormalDefines.php)119
-rw-r--r--includes/libs/normal/UtfNormalUtil.php (renamed from includes/normal/UtfNormalUtil.php)83
-rw-r--r--includes/libs/objectcache/APCBagOStuff.php (renamed from includes/objectcache/APCBagOStuff.php)41
-rw-r--r--includes/libs/objectcache/BagOStuff.php (renamed from includes/objectcache/BagOStuff.php)104
-rw-r--r--includes/libs/objectcache/EmptyBagOStuff.php (renamed from includes/objectcache/EmptyBagOStuff.php)45
-rw-r--r--includes/libs/objectcache/HashBagOStuff.php (renamed from includes/objectcache/HashBagOStuff.php)54
-rw-r--r--includes/libs/objectcache/WinCacheBagOStuff.php (renamed from includes/objectcache/WinCacheBagOStuff.php)13
-rw-r--r--includes/libs/objectcache/XCacheBagOStuff.php (renamed from includes/objectcache/XCacheBagOStuff.php)30
-rw-r--r--includes/libs/replacers/DoubleReplacer.php43
-rw-r--r--includes/libs/replacers/HashtableReplacer.php44
-rw-r--r--includes/libs/replacers/RegexlikeReplacer.php46
-rw-r--r--includes/libs/replacers/Replacer.php (renamed from includes/resourceloader/ResourceLoaderFilePageModule.php)21
-rw-r--r--includes/libs/virtualrest/ParsoidVirtualRESTService.php126
-rw-r--r--includes/libs/virtualrest/RestbaseVirtualRESTService.php177
-rw-r--r--includes/libs/virtualrest/VirtualRESTServiceClient.php31
-rw-r--r--includes/logging/BlockLogFormatter.php224
-rw-r--r--includes/logging/DeleteLogFormatter.php78
-rw-r--r--includes/logging/LogEntry.php38
-rw-r--r--includes/logging/LogEventsList.php76
-rw-r--r--includes/logging/LogFormatter.php253
-rw-r--r--includes/logging/LogPage.php156
-rw-r--r--includes/logging/LogPager.php2
-rw-r--r--includes/logging/MergeLogFormatter.php91
-rw-r--r--includes/logging/MoveLogFormatter.php29
-rw-r--r--includes/logging/PatrolLogFormatter.php20
-rw-r--r--includes/logging/RightsLogFormatter.php65
-rw-r--r--includes/logging/TagLogFormatter.php49
-rw-r--r--includes/logging/UploadLogFormatter.php49
-rw-r--r--includes/mail/EmailNotification.php10
-rw-r--r--includes/mail/MailAddress.php2
-rw-r--r--includes/mail/UserMailer.php4
-rw-r--r--includes/media/BMP.php2
-rw-r--r--includes/media/Bitmap.php14
-rw-r--r--includes/media/BitmapMetadataHandler.php2
-rw-r--r--includes/media/DjVu.php5
-rw-r--r--includes/media/DjVuImage.php16
-rw-r--r--includes/media/Exif.php2
-rw-r--r--includes/media/ExifBitmap.php5
-rw-r--r--includes/media/FormatMetadata.php98
-rw-r--r--includes/media/GIF.php5
-rw-r--r--includes/media/GIFMetadataExtractor.php2
-rw-r--r--includes/media/IPTC.php2
-rw-r--r--includes/media/ImageHandler.php6
-rw-r--r--includes/media/Jpeg.php4
-rw-r--r--includes/media/JpegMetadataExtractor.php4
-rw-r--r--includes/media/MediaHandler.php34
-rw-r--r--includes/media/MediaTransformInvalidParametersException.php26
-rw-r--r--includes/media/MediaTransformOutput.php40
-rw-r--r--includes/media/PNG.php10
-rw-r--r--includes/media/SVG.php9
-rw-r--r--includes/media/SVGMetadataExtractor.php20
-rw-r--r--includes/media/Tiff.php2
-rw-r--r--includes/media/TransformationalImageHandler.php75
-rw-r--r--includes/media/XCF.php2
-rw-r--r--includes/media/XMP.php13
-rw-r--r--includes/media/XMPInfo.php2
-rw-r--r--includes/mime.info1
-rw-r--r--includes/mime.types3
-rw-r--r--includes/normal/Makefile78
-rw-r--r--includes/normal/README59
-rw-r--r--includes/normal/RandomTest.php102
-rw-r--r--includes/normal/Utf8Test.php156
-rw-r--r--includes/normal/UtfNormal.php790
-rw-r--r--includes/normal/UtfNormalBench.php105
-rw-r--r--includes/normal/UtfNormalData.inc14
-rw-r--r--includes/normal/UtfNormalDataK.inc11
-rw-r--r--includes/normal/UtfNormalGenerate.php250
-rw-r--r--includes/normal/UtfNormalMemStress.php107
-rw-r--r--includes/normal/UtfNormalTest.php257
-rw-r--r--includes/normal/UtfNormalTest2.php275
-rw-r--r--includes/objectcache/MemcachedBagOStuff.php19
-rw-r--r--includes/objectcache/MemcachedClient.php20
-rw-r--r--includes/objectcache/MemcachedPeclBagOStuff.php25
-rw-r--r--includes/objectcache/MemcachedPhpBagOStuff.php18
-rw-r--r--includes/objectcache/MultiWriteBagOStuff.php30
-rw-r--r--includes/objectcache/ObjectCache.php11
-rw-r--r--includes/objectcache/ObjectCacheSessionHandler.php38
-rw-r--r--includes/objectcache/RedisBagOStuff.php24
-rw-r--r--includes/objectcache/SqlBagOStuff.php75
-rw-r--r--includes/page/Article.php350
-rw-r--r--includes/page/CategoryPage.php6
-rw-r--r--includes/page/ImagePage.php100
-rw-r--r--includes/page/WikiPage.php476
-rw-r--r--includes/pager/IndexPager.php5
-rw-r--r--includes/parser/CacheTime.php26
-rw-r--r--includes/parser/CoreParserFunctions.php92
-rw-r--r--includes/parser/CoreTagHooks.php27
-rw-r--r--includes/parser/DateFormatter.php15
-rw-r--r--includes/parser/LinkHolderArray.php25
-rw-r--r--includes/parser/MWTidy.php76
-rw-r--r--includes/parser/Parser.php686
-rw-r--r--includes/parser/ParserCache.php7
-rw-r--r--includes/parser/ParserOptions.php97
-rw-r--r--includes/parser/ParserOutput.php141
-rw-r--r--includes/parser/Preprocessor_DOM.php30
-rw-r--r--includes/parser/Preprocessor_Hash.php20
-rw-r--r--includes/parser/StripState.php2
-rw-r--r--includes/password/PasswordFactory.php15
-rw-r--r--includes/poolcounter/PoolCounter.php57
-rw-r--r--includes/poolcounter/PoolCounterRedis.php17
-rw-r--r--includes/poolcounter/PoolWorkArticleView.php7
-rw-r--r--includes/profiler/ProfileSection.php43
-rw-r--r--includes/profiler/Profiler.php499
-rw-r--r--includes/profiler/ProfilerFunctions.php56
-rw-r--r--includes/profiler/ProfilerMwprof.php256
-rw-r--r--includes/profiler/ProfilerSectionOnly.php104
-rw-r--r--includes/profiler/ProfilerSimpleText.php82
-rw-r--r--includes/profiler/ProfilerSimpleTrace.php85
-rw-r--r--includes/profiler/ProfilerSimpleUDP.php75
-rw-r--r--includes/profiler/ProfilerStandard.php559
-rw-r--r--includes/profiler/ProfilerStub.php25
-rw-r--r--includes/profiler/ProfilerXhprof.php194
-rw-r--r--includes/profiler/SectionProfiler.php530
-rw-r--r--includes/profiler/TransactionProfiler.php274
-rw-r--r--includes/profiler/output/ProfilerOutput.php57
-rw-r--r--includes/profiler/output/ProfilerOutputDb.php (renamed from includes/profiler/ProfilerSimpleDB.php)87
-rw-r--r--includes/profiler/output/ProfilerOutputDump.php51
-rw-r--r--includes/profiler/output/ProfilerOutputStats.php57
-rw-r--r--includes/profiler/output/ProfilerOutputText.php77
-rw-r--r--includes/profiler/output/ProfilerOutputUdp.php96
-rw-r--r--includes/rcfeed/IRCColourfulRCFeedFormatter.php2
-rw-r--r--includes/rcfeed/UDPRCFeedEngine.php3
-rw-r--r--includes/registration/ExtensionProcessor.php299
-rw-r--r--includes/registration/ExtensionRegistry.php254
-rw-r--r--includes/registration/Processor.php27
-rw-r--r--includes/resourceloader/DerivativeResourceLoaderContext.php1
-rw-r--r--includes/resourceloader/ResourceLoader.php323
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php91
-rw-r--r--includes/resourceloader/ResourceLoaderEditToolbarModule.php1
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php109
-rw-r--r--includes/resourceloader/ResourceLoaderImage.php388
-rw-r--r--includes/resourceloader/ResourceLoaderImageModule.php327
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageDataModule.php12
-rw-r--r--includes/resourceloader/ResourceLoaderLanguageNamesModule.php14
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php87
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php13
-rw-r--r--includes/resourceloader/ResourceLoaderSkinModule.php92
-rw-r--r--includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php107
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php118
-rw-r--r--includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php14
-rw-r--r--includes/resourceloader/ResourceLoaderUserDefaultsModule.php62
-rw-r--r--includes/resourceloader/ResourceLoaderUserGroupsModule.php26
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php36
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php15
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php13
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php90
-rw-r--r--includes/revisiondelete/RevDelArchiveList.php2
-rw-r--r--includes/revisiondelete/RevDelArchivedFileList.php2
-rw-r--r--includes/revisiondelete/RevDelFileList.php2
-rw-r--r--includes/revisiondelete/RevDelList.php28
-rw-r--r--includes/revisiondelete/RevDelLogItem.php10
-rw-r--r--includes/revisiondelete/RevDelLogList.php8
-rw-r--r--includes/revisiondelete/RevDelRevisionList.php4
-rw-r--r--includes/revisiondelete/RevisionDeleteUser.php4
-rw-r--r--includes/revisiondelete/RevisionDeleter.php3
-rw-r--r--includes/search/SearchEngine.php64
-rw-r--r--includes/search/SearchHighlighter.php26
-rw-r--r--includes/search/SearchMySQL.php4
-rw-r--r--includes/search/SearchPostgres.php2
-rw-r--r--includes/search/SearchResult.php9
-rw-r--r--includes/site/CachingSiteStore.php195
-rw-r--r--includes/site/DBSiteStore.php345
-rw-r--r--includes/site/FileBasedSiteLookup.php139
-rw-r--r--includes/site/HashSiteStore.php123
-rw-r--r--includes/site/MediaWikiSite.php4
-rw-r--r--includes/site/SiteExporter.php114
-rw-r--r--includes/site/SiteImporter.php263
-rw-r--r--includes/site/SiteLookup.php (renamed from includes/resourceloader/ResourceLoaderNoscriptModule.php)44
-rw-r--r--includes/site/SiteSQLStore.php459
-rw-r--r--includes/site/SiteStore.php29
-rw-r--r--includes/site/SitesCacheFileBuilder.php113
-rw-r--r--includes/skins/BaseTemplate.php644
-rw-r--r--includes/skins/MediaWikiI18N.php52
-rw-r--r--includes/skins/QuickTemplate.php194
-rw-r--r--includes/skins/Skin.php176
-rw-r--r--includes/skins/SkinApi.php71
-rw-r--r--includes/skins/SkinApiTemplate.php63
-rw-r--r--includes/skins/SkinFactory.php110
-rw-r--r--includes/skins/SkinFallbackTemplate.php29
-rw-r--r--includes/skins/SkinTemplate.php908
-rw-r--r--includes/specialpage/ChangesListSpecialPage.php18
-rw-r--r--includes/specialpage/FormSpecialPage.php29
-rw-r--r--includes/specialpage/ImageQueryPage.php4
-rw-r--r--includes/specialpage/PageQueryPage.php2
-rw-r--r--includes/specialpage/QueryPage.php17
-rw-r--r--includes/specialpage/RedirectSpecialPage.php2
-rw-r--r--includes/specialpage/SpecialPage.php55
-rw-r--r--includes/specialpage/SpecialPageFactory.php40
-rw-r--r--includes/specialpage/WantedQueryPage.php4
-rw-r--r--includes/specials/SpecialActiveusers.php174
-rw-r--r--includes/specials/SpecialAllMessages.php14
-rw-r--r--includes/specials/SpecialAllPages.php5
-rw-r--r--includes/specials/SpecialApiHelp.php93
-rw-r--r--includes/specials/SpecialBlock.php54
-rw-r--r--includes/specials/SpecialBlockList.php17
-rw-r--r--includes/specials/SpecialBooksources.php23
-rw-r--r--includes/specials/SpecialCategories.php6
-rw-r--r--includes/specials/SpecialChangeEmail.php34
-rw-r--r--includes/specials/SpecialChangePassword.php14
-rw-r--r--includes/specials/SpecialConfirmemail.php3
-rw-r--r--includes/specials/SpecialContributions.php59
-rw-r--r--includes/specials/SpecialDeletedContributions.php113
-rw-r--r--includes/specials/SpecialDiff.php1
-rw-r--r--includes/specials/SpecialEditTags.php460
-rw-r--r--includes/specials/SpecialEditWatchlist.php42
-rw-r--r--includes/specials/SpecialEmailuser.php12
-rw-r--r--includes/specials/SpecialExpandTemplates.php5
-rw-r--r--includes/specials/SpecialExport.php9
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php52
-rw-r--r--includes/specials/SpecialFilepath.php1
-rw-r--r--includes/specials/SpecialImport.php43
-rw-r--r--includes/specials/SpecialJavaScriptTest.php33
-rw-r--r--includes/specials/SpecialLinkSearch.php52
-rw-r--r--includes/specials/SpecialListDuplicatedFiles.php2
-rw-r--r--includes/specials/SpecialListfiles.php12
-rw-r--r--includes/specials/SpecialListgrouprights.php78
-rw-r--r--includes/specials/SpecialListredirects.php2
-rw-r--r--includes/specials/SpecialListusers.php71
-rw-r--r--includes/specials/SpecialLog.php115
-rw-r--r--includes/specials/SpecialLonelypages.php2
-rw-r--r--includes/specials/SpecialMediaStatistics.php22
-rw-r--r--includes/specials/SpecialMergeHistory.php27
-rw-r--r--includes/specials/SpecialMostcategories.php2
-rw-r--r--includes/specials/SpecialMostimages.php2
-rw-r--r--includes/specials/SpecialMostinterwikis.php2
-rw-r--r--includes/specials/SpecialMostlinked.php2
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php2
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php2
-rw-r--r--includes/specials/SpecialMovepage.php226
-rw-r--r--includes/specials/SpecialMyLanguage.php5
-rw-r--r--includes/specials/SpecialNewimages.php14
-rw-r--r--includes/specials/SpecialNewpages.php135
-rw-r--r--includes/specials/SpecialPageLanguage.php8
-rw-r--r--includes/specials/SpecialPagesWithProp.php49
-rw-r--r--includes/specials/SpecialPasswordReset.php15
-rw-r--r--includes/specials/SpecialPopularpages.php89
-rw-r--r--includes/specials/SpecialPreferences.php2
-rw-r--r--includes/specials/SpecialPrefixindex.php5
-rw-r--r--includes/specials/SpecialProtectedpages.php17
-rw-r--r--includes/specials/SpecialProtectedtitles.php14
-rw-r--r--includes/specials/SpecialRandomInCategory.php10
-rw-r--r--includes/specials/SpecialRandompage.php2
-rw-r--r--includes/specials/SpecialRecentchanges.php23
-rw-r--r--includes/specials/SpecialRedirect.php15
-rw-r--r--includes/specials/SpecialResetTokens.php2
-rw-r--r--includes/specials/SpecialRevisiondelete.php34
-rw-r--r--includes/specials/SpecialRunJobs.php11
-rw-r--r--includes/specials/SpecialSearch.php131
-rw-r--r--includes/specials/SpecialShortpages.php2
-rw-r--r--includes/specials/SpecialSpecialpages.php1
-rw-r--r--includes/specials/SpecialStatistics.php100
-rw-r--r--includes/specials/SpecialTags.php336
-rw-r--r--includes/specials/SpecialTrackingCategories.php142
-rw-r--r--includes/specials/SpecialUnblock.php11
-rw-r--r--includes/specials/SpecialUndelete.php32
-rw-r--r--includes/specials/SpecialUpload.php99
-rw-r--r--includes/specials/SpecialUploadStash.php14
-rw-r--r--includes/specials/SpecialUserlogin.php103
-rw-r--r--includes/specials/SpecialUserlogout.php2
-rw-r--r--includes/specials/SpecialUserrights.php65
-rw-r--r--includes/specials/SpecialVersion.php151
-rw-r--r--includes/specials/SpecialWantedcategories.php7
-rw-r--r--includes/specials/SpecialWantedfiles.php4
-rw-r--r--includes/specials/SpecialWantedpages.php7
-rw-r--r--includes/specials/SpecialWatchlist.php60
-rw-r--r--includes/specials/SpecialWhatlinkshere.php67
-rw-r--r--includes/templates/NoLocalSettings.mustache39
-rw-r--r--includes/templates/Usercreate.php25
-rw-r--r--includes/templates/Userlogin.php75
-rw-r--r--includes/title/ForeignTitle.php117
-rw-r--r--includes/title/ForeignTitleFactory.php36
-rw-r--r--includes/title/ImportTitleFactory.php36
-rw-r--r--includes/title/MalformedTitleException.php1
-rw-r--r--includes/title/MediaWikiPageLinkRenderer.php1
-rw-r--r--includes/title/MediaWikiTitleCodec.php34
-rw-r--r--includes/title/NaiveForeignTitleFactory.php71
-rw-r--r--includes/title/NaiveImportTitleFactory.php65
-rw-r--r--includes/title/NamespaceAwareForeignTitleFactory.php134
-rw-r--r--includes/title/NamespaceImportTitleFactory.php52
-rw-r--r--includes/title/PageLinkRenderer.php1
-rw-r--r--includes/title/SubpageImportTitleFactory.php55
-rw-r--r--includes/title/TitleFormatter.php1
-rw-r--r--includes/title/TitleParser.php1
-rw-r--r--includes/title/TitleValue.php1
-rw-r--r--includes/upload/UploadBase.php161
-rw-r--r--includes/upload/UploadFromChunks.php20
-rw-r--r--includes/upload/UploadFromUrl.php26
-rw-r--r--includes/upload/UploadStash.php4
-rw-r--r--includes/utils/AutoloadGenerator.php296
-rw-r--r--includes/utils/Cdb.php163
-rw-r--r--includes/utils/CdbDBA.php75
-rw-r--r--includes/utils/CdbPHP.php494
-rw-r--r--includes/utils/IP.php23
-rw-r--r--includes/utils/MWCryptHKDF.php11
-rw-r--r--includes/utils/MWCryptRand.php11
-rw-r--r--includes/utils/MWFunction.php37
-rw-r--r--includes/utils/UIDGenerator.php6
-rw-r--r--includes/utils/ZipDirectoryReader.php1
827 files changed, 52489 insertions, 33391 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index 9bc92be9..b14114d7 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -56,8 +56,6 @@ class AjaxDispatcher {
* Load up our object with user supplied data
*/
function __construct( Config $config ) {
- wfProfileIn( __METHOD__ );
-
$this->config = $config;
$this->mode = "";
@@ -88,13 +86,11 @@ class AjaxDispatcher {
}
break;
default:
- wfProfileOut( __METHOD__ );
return;
# Or we could throw an exception:
# throw new MWException( __METHOD__ . ' called without any data (mode empty).' );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -110,11 +106,8 @@ class AjaxDispatcher {
return;
}
- wfProfileIn( __METHOD__ );
-
if ( !in_array( $this->func_name, $this->config->get( 'AjaxExportList' ) ) ) {
wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" );
-
wfHttpError(
400,
'Bad Request',
@@ -127,7 +120,6 @@ class AjaxDispatcher {
'You are not allowed to view pages.' );
} else {
wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" );
-
try {
$result = call_user_func_array( $this->func_name, $this->args );
@@ -162,6 +154,5 @@ class AjaxDispatcher {
}
}
- wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 6b0daa14..6344c276 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -25,1199 +25,7 @@
* Extension classes are specified with $wgAutoloadClasses
* This array is a global instead of a static member of AutoLoader to work around a bug in APC
*/
-global $wgAutoloadLocalClasses;
-
-$wgAutoloadLocalClasses = array(
- # Includes
- 'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
- 'AjaxResponse' => 'includes/AjaxResponse.php',
- 'AtomFeed' => 'includes/Feed.php',
- 'AuthPlugin' => 'includes/AuthPlugin.php',
- 'AuthPluginUser' => 'includes/AuthPlugin.php',
- 'Autopromote' => 'includes/Autopromote.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',
- 'CategoryViewer' => 'includes/CategoryViewer.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',
- 'Cookie' => 'includes/Cookie.php',
- 'CookieJar' => 'includes/Cookie.php',
- 'CurlHttpRequest' => 'includes/HttpFunctions.php',
- 'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
- 'DerivativeRequest' => 'includes/WebRequest.php',
- 'DiffHistoryBlob' => 'includes/HistoryBlob.php',
- 'DummyLinker' => 'includes/Linker.php',
- 'Dump7ZipOutput' => 'includes/Export.php',
- 'DumpBZip2Output' => 'includes/Export.php',
- 'DumpFileOutput' => 'includes/Export.php',
- 'DumpFilter' => 'includes/Export.php',
- 'DumpGZipOutput' => 'includes/Export.php',
- 'DumpLatestFilter' => 'includes/Export.php',
- 'DumpMultiWriter' => 'includes/Export.php',
- 'DumpNamespaceFilter' => 'includes/Export.php',
- 'DumpNotalkFilter' => 'includes/Export.php',
- 'DumpOutput' => 'includes/Export.php',
- 'DumpPipeOutput' => 'includes/Export.php',
- 'EditPage' => 'includes/EditPage.php',
- 'EmptyBloomCache' => 'includes/cache/bloom/BloomCache.php',
- 'Fallback' => 'includes/Fallback.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',
- 'FormOptions' => 'includes/FormOptions.php',
- 'GitInfo' => 'includes/GitInfo.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/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',
- 'IcuCollation' => 'includes/Collation.php',
- 'IdentityCollation' => 'includes/Collation.php',
- 'ImportStreamSource' => 'includes/Import.php',
- 'ImportStringSource' => 'includes/Import.php',
- 'Interwiki' => 'includes/interwiki/Interwiki.php',
- 'License' => 'includes/Licenses.php',
- 'Licenses' => 'includes/Licenses.php',
- 'Linker' => 'includes/Linker.php',
- 'LinkFilter' => 'includes/LinkFilter.php',
- 'MagicWord' => 'includes/MagicWord.php',
- 'MagicWordArray' => 'includes/MagicWord.php',
- 'MediaWiki' => 'includes/MediaWiki.php',
- 'MediaWikiVersionFetcher' => 'includes/MediaWikiVersionFetcher.php',
- 'Message' => 'includes/Message.php',
- 'MessageBlobStore' => 'includes/MessageBlobStore.php',
- 'MimeMagic' => 'includes/MimeMagic.php',
- 'MovePage' => 'includes/MovePage.php',
- 'MWHookException' => 'includes/Hooks.php',
- 'MWHttpRequest' => 'includes/HttpFunctions.php',
- 'MWNamespace' => 'includes/MWNamespace.php',
- 'OutputPage' => 'includes/OutputPage.php',
- 'PathRouter' => 'includes/PathRouter.php',
- 'PathRouterPatternReplacer' => 'includes/PathRouter.php',
- 'PhpHttpRequest' => 'includes/HttpFunctions.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',
- 'RawMessage' => 'includes/Message.php',
- 'RevisionItem' => 'includes/RevisionList.php',
- 'RevisionItemBase' => 'includes/RevisionList.php',
- 'RevisionListBase' => 'includes/RevisionList.php',
- 'Revision' => 'includes/Revision.php',
- 'RevisionList' => 'includes/RevisionList.php',
- 'RSSFeed' => 'includes/Feed.php',
- 'Sanitizer' => 'includes/Sanitizer.php',
- 'SiteConfiguration' => 'includes/SiteConfiguration.php',
- 'SiteStats' => 'includes/SiteStats.php',
- 'SiteStatsInit' => 'includes/SiteStats.php',
- 'SquidPurgeClient' => 'includes/SquidPurgeClient.php',
- 'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
- 'StatCounter' => 'includes/StatCounter.php',
- 'Status' => 'includes/Status.php',
- 'StreamFile' => 'includes/StreamFile.php',
- 'StringPrefixSearch' => 'includes/PrefixSearch.php',
- 'StubObject' => 'includes/StubObject.php',
- 'StubUserLang' => 'includes/StubObject.php',
- 'MWTimestamp' => 'includes/MWTimestamp.php',
- 'TimestampException' => 'includes/TimestampException.php',
- 'Title' => 'includes/Title.php',
- 'TitleArray' => 'includes/TitleArray.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/UserArrayFromResult.php',
- 'UserRightsProxy' => 'includes/UserRightsProxy.php',
- 'WatchedItem' => 'includes/WatchedItem.php',
- 'WebRequest' => 'includes/WebRequest.php',
- 'WebRequestUpload' => 'includes/WebRequest.php',
- 'WebResponse' => 'includes/WebResponse.php',
- 'WikiExporter' => 'includes/Export.php',
- 'WikiImporter' => 'includes/Import.php',
- 'WikiRevision' => 'includes/Import.php',
- 'WikiMap' => 'includes/WikiMap.php',
- 'WikiReference' => 'includes/WikiMap.php',
- 'Xml' => 'includes/Xml.php',
- 'XmlDumpWriter' => 'includes/Export.php',
- 'XmlJsCode' => 'includes/Xml.php',
- 'XmlSelect' => 'includes/Xml.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',
- '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',
- 'RenderAction' => 'includes/actions/RenderAction.php',
- 'RevertAction' => 'includes/actions/RevertAction.php',
- 'RevisiondeleteAction' => 'includes/actions/RevisiondeleteAction.php',
- 'RollbackAction' => 'includes/actions/RollbackAction.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',
- 'ApiDisabled' => 'includes/api/ApiDisabled.php',
- 'ApiEditPage' => 'includes/api/ApiEditPage.php',
- '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/ApiFormatFeedWrapper.php',
- 'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
- 'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
- 'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
- 'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
- 'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
- 'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php',
- 'ApiFormatXml' => 'includes/api/ApiFormatXml.php',
- 'ApiFormatXmlRsd' => 'includes/api/ApiRsd.php',
- 'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php',
- 'ApiHelp' => 'includes/api/ApiHelp.php',
- 'ApiImageRotate' => 'includes/api/ApiImageRotate.php',
- 'ApiImport' => 'includes/api/ApiImport.php',
- 'ApiImportReporter' => 'includes/api/ApiImport.php',
- 'ApiLogin' => 'includes/api/ApiLogin.php',
- 'ApiLogout' => 'includes/api/ApiLogout.php',
- 'ApiMain' => 'includes/api/ApiMain.php',
- 'ApiModuleManager' => 'includes/api/ApiModuleManager.php',
- 'ApiMove' => 'includes/api/ApiMove.php',
- 'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php',
- 'ApiOptions' => 'includes/api/ApiOptions.php',
- 'ApiPageSet' => 'includes/api/ApiPageSet.php',
- 'ApiParamInfo' => 'includes/api/ApiParamInfo.php',
- 'ApiParse' => 'includes/api/ApiParse.php',
- 'ApiPatrol' => 'includes/api/ApiPatrol.php',
- 'ApiProtect' => 'includes/api/ApiProtect.php',
- 'ApiPurge' => 'includes/api/ApiPurge.php',
- 'ApiQuery' => 'includes/api/ApiQuery.php',
- 'ApiQueryAllCategories' => 'includes/api/ApiQueryAllCategories.php',
- 'ApiQueryAllImages' => 'includes/api/ApiQueryAllImages.php',
- 'ApiQueryAllLinks' => 'includes/api/ApiQueryAllLinks.php',
- 'ApiQueryAllMessages' => 'includes/api/ApiQueryAllMessages.php',
- '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',
- 'ApiQueryExternalLinks' => 'includes/api/ApiQueryExternalLinks.php',
- 'ApiQueryExtLinksUsage' => 'includes/api/ApiQueryExtLinksUsage.php',
- 'ApiQueryFilearchive' => 'includes/api/ApiQueryFilearchive.php',
- 'ApiQueryGeneratorBase' => 'includes/api/ApiQueryBase.php',
- 'ApiQueryImageInfo' => 'includes/api/ApiQueryImageInfo.php',
- 'ApiQueryImages' => 'includes/api/ApiQueryImages.php',
- 'ApiQueryInfo' => 'includes/api/ApiQueryInfo.php',
- 'ApiQueryIWBacklinks' => 'includes/api/ApiQueryIWBacklinks.php',
- 'ApiQueryIWLinks' => 'includes/api/ApiQueryIWLinks.php',
- 'ApiQueryLangBacklinks' => 'includes/api/ApiQueryLangBacklinks.php',
- 'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php',
- 'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php',
- 'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php',
- 'ApiQueryORM' => 'includes/api/ApiQueryORM.php',
- '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',
- 'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php',
- 'ApiQueryFileRepoInfo' => 'includes/api/ApiQueryFileRepoInfo.php',
- 'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
- 'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php',
- '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',
- 'ApiTokens' => 'includes/api/ApiTokens.php',
- 'ApiUnblock' => 'includes/api/ApiUnblock.php',
- 'ApiUndelete' => 'includes/api/ApiUndelete.php',
- 'ApiUpload' => 'includes/api/ApiUpload.php',
- 'ApiUserrights' => 'includes/api/ApiUserrights.php',
- 'ApiWatch' => 'includes/api/ApiWatch.php',
- 'UsageException' => 'includes/api/ApiMain.php',
-
- # 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',
- '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',
- 'ResourceFileCache' => 'includes/cache/ResourceFileCache.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',
- 'IContextSource' => 'includes/context/IContextSource.php',
- 'RequestContext' => 'includes/context/RequestContext.php',
-
- # includes/dao
- 'IDBAccessObject' => 'includes/dao/IDBAccessObject.php',
- 'DBAccessBase' => 'includes/dao/DBAccessBase.php',
-
- # includes/db
- 'Blob' => 'includes/db/DatabaseUtility.php',
- 'ChronologyProtector' => 'includes/db/ChronologyProtector.php',
- 'CloneDatabase' => 'includes/db/CloneDatabase.php',
- 'DatabaseBase' => 'includes/db/Database.php',
- 'DatabaseMssql' => 'includes/db/DatabaseMssql.php',
- 'DatabaseMysql' => 'includes/db/DatabaseMysql.php',
- 'DatabaseMysqlBase' => 'includes/db/DatabaseMysqlBase.php',
- 'DatabaseMysqli' => 'includes/db/DatabaseMysqli.php',
- 'DatabaseOracle' => 'includes/db/DatabaseOracle.php',
- 'DatabasePostgres' => 'includes/db/DatabasePostgres.php',
- 'DatabaseSqlite' => 'includes/db/DatabaseSqlite.php',
- 'DatabaseSqliteStandalone' => 'includes/db/DatabaseSqlite.php',
- 'DatabaseType' => 'includes/db/Database.php',
- 'DBAccessError' => 'includes/db/LBFactory.php',
- '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',
- 'IORMTable' => 'includes/db/IORMTable.php',
- 'DBMasterPos' => 'includes/db/DatabaseUtility.php',
- 'DBQueryError' => 'includes/db/DatabaseError.php',
- 'DBUnexpectedError' => 'includes/db/DatabaseError.php',
- 'FakeResultWrapper' => 'includes/db/DatabaseUtility.php',
- 'Field' => 'includes/db/DatabaseUtility.php',
- 'LBFactory' => 'includes/db/LBFactory.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',
- 'LoadBalancerSingle' => 'includes/db/LBFactorySingle.php',
- 'LoadMonitor' => 'includes/db/LoadMonitor.php',
- 'LoadMonitorMySQL' => 'includes/db/LoadMonitor.php',
- 'LoadMonitorNull' => 'includes/db/LoadMonitor.php',
- 'MssqlField' => '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',
- 'ORAResult' => 'includes/db/DatabaseOracle.php',
- 'ORMIterator' => 'includes/db/ORMIterator.php',
- 'ORMResult' => 'includes/db/ORMResult.php',
- 'ORMRow' => 'includes/db/ORMRow.php',
- 'ORMTable' => 'includes/db/ORMTable.php',
- 'PostgresField' => 'includes/db/DatabasePostgres.php',
- 'PostgresTransactionState' => 'includes/db/DatabasePostgres.php',
- 'ResultWrapper' => 'includes/db/DatabaseUtility.php',
- 'SavepointPostgres' => 'includes/db/DatabasePostgres.php',
- 'SQLiteField' => 'includes/db/DatabaseSqlite.php',
-
- # includes/debug
- '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',
- '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/DiffFormatter.php',
- 'MappedDiff' => 'includes/diff/DairikiDiff.php',
- 'RangeDifference' => 'includes/diff/WikiDiff3.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',
- 'ExternalStoreHttp' => 'includes/externalstore/ExternalStoreHttp.php',
- 'ExternalStoreMedium' => 'includes/externalstore/ExternalStoreMedium.php',
- 'ExternalStoreMwstore' => 'includes/externalstore/ExternalStoreMwstore.php',
-
- # includes/filebackend
- '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',
- 'FileBackendStoreShardFileIterator' => 'includes/filebackend/FileBackendStore.php',
- 'FileBackendMultiWrite' => 'includes/filebackend/FileBackendMultiWrite.php',
- 'FileBackendStoreOpHandle' => 'includes/filebackend/FileBackendStore.php',
- 'FSFile' => 'includes/filebackend/FSFile.php',
- 'FSFileBackend' => 'includes/filebackend/FSFileBackend.php',
- 'FSFileBackendList' => 'includes/filebackend/FSFileBackend.php',
- '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',
- 'SwiftFileBackendFileList' => 'includes/filebackend/SwiftFileBackend.php',
- 'SwiftFileOpHandle' => 'includes/filebackend/SwiftFileBackend.php',
- 'TempFSFile' => 'includes/filebackend/TempFSFile.php',
- 'FileJournal' => 'includes/filebackend/filejournal/FileJournal.php',
- 'DBFileJournal' => 'includes/filebackend/filejournal/DBFileJournal.php',
- 'NullFileJournal' => 'includes/filebackend/filejournal/FileJournal.php',
- 'LockManagerGroup' => 'includes/filebackend/lockmanager/LockManagerGroup.php',
- 'LockManager' => 'includes/filebackend/lockmanager/LockManager.php',
- 'ScopedLock' => 'includes/filebackend/lockmanager/ScopedLock.php',
- 'FSLockManager' => 'includes/filebackend/lockmanager/FSLockManager.php',
- 'DBLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
- 'MemcLockManager' => 'includes/filebackend/lockmanager/MemcLockManager.php',
- 'QuorumLockManager' => 'includes/filebackend/lockmanager/QuorumLockManager.php',
- 'MySqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
- 'PostgreSqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php',
- 'RedisLockManager' => 'includes/filebackend/lockmanager/RedisLockManager.php',
- 'NullLockManager' => 'includes/filebackend/lockmanager/LockManager.php',
- 'FileOp' => 'includes/filebackend/FileOp.php',
- 'FileOpBatch' => 'includes/filebackend/FileOpBatch.php',
- 'StoreFileOp' => 'includes/filebackend/FileOp.php',
- 'CopyFileOp' => 'includes/filebackend/FileOp.php',
- 'MoveFileOp' => 'includes/filebackend/FileOp.php',
- 'DeleteFileOp' => 'includes/filebackend/FileOp.php',
- 'CreateFileOp' => 'includes/filebackend/FileOp.php',
- 'DescribeFileOp' => 'includes/filebackend/FileOp.php',
- 'NullFileOp' => 'includes/filebackend/FileOp.php',
-
- # includes/filerepo
- 'FileRepo' => 'includes/filerepo/FileRepo.php',
- 'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php',
- 'ForeignAPIRepo' => 'includes/filerepo/ForeignAPIRepo.php',
- 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php',
- 'ForeignDBViaLBRepo' => 'includes/filerepo/ForeignDBViaLBRepo.php',
- 'FSRepo' => 'includes/filerepo/FSRepo.php',
- 'LocalRepo' => 'includes/filerepo/LocalRepo.php',
- 'NullRepo' => 'includes/filerepo/NullRepo.php',
- 'RepoGroup' => 'includes/filerepo/RepoGroup.php',
- 'TempFileRepo' => 'includes/filerepo/FileRepo.php',
-
- # includes/filerepo/file
- 'ArchivedFile' => 'includes/filerepo/file/ArchivedFile.php',
- 'File' => 'includes/filerepo/file/File.php',
- 'ForeignAPIFile' => 'includes/filerepo/file/ForeignAPIFile.php',
- 'ForeignDBFile' => 'includes/filerepo/file/ForeignDBFile.php',
- 'LocalFile' => 'includes/filerepo/file/LocalFile.php',
- 'LocalFileDeleteBatch' => 'includes/filerepo/file/LocalFile.php',
- 'LocalFileMoveBatch' => 'includes/filerepo/file/LocalFile.php',
- 'LocalFileRestoreBatch' => 'includes/filerepo/file/LocalFile.php',
- 'OldLocalFile' => 'includes/filerepo/file/OldLocalFile.php',
- 'UnregisteredLocalFile' => 'includes/filerepo/file/UnregisteredLocalFile.php',
-
- # includes/installer
- 'CliInstaller' => 'includes/installer/CliInstaller.php',
- 'DatabaseInstaller' => 'includes/installer/DatabaseInstaller.php',
- 'DatabaseUpdater' => 'includes/installer/DatabaseUpdater.php',
- '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',
- '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',
- '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
- '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',
- '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',
- 'JSNode' => 'includes/libs/jsminplus.php',
- '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',
- 'lessc_parser' => 'includes/libs/lessc.inc.php',
- 'lessc_formatter_classic' => 'includes/libs/lessc.inc.php',
- 'lessc_formatter_compressed' => 'includes/libs/lessc.inc.php',
- 'lessc_formatter_lessjs' => 'includes/libs/lessc.inc.php',
-
- # includes/logging
- 'DatabaseLogEntry' => 'includes/logging/LogEntry.php',
- 'DeleteLogFormatter' => 'includes/logging/DeleteLogFormatter.php',
- 'LegacyLogFormatter' => 'includes/logging/LogFormatter.php',
- 'LogEntry' => 'includes/logging/LogEntry.php',
- 'LogEventsList' => 'includes/logging/LogEventsList.php',
- 'LogEntryBase' => 'includes/logging/LogEntry.php',
- 'LogFormatter' => 'includes/logging/LogFormatter.php',
- 'LogPage' => 'includes/logging/LogPage.php',
- 'LogPager' => 'includes/logging/LogPager.php',
- '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',
- 'RightsLogFormatter' => 'includes/logging/RightsLogFormatter.php',
-
- # Image gallery
-
- 'ImageGallery' => 'includes/gallery/TraditionalImageGallery.php',
- 'ImageGalleryBase' => 'includes/gallery/ImageGalleryBase.php',
- 'NolinesImageGallery' => 'includes/gallery/NolinesImageGallery.php',
- 'TraditionalImageGallery' => 'includes/gallery/TraditionalImageGallery.php',
- 'PackedImageGallery' => 'includes/gallery/PackedImageGallery.php',
- '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',
- 'BitmapMetadataHandler' => 'includes/media/BitmapMetadataHandler.php',
- 'BmpHandler' => 'includes/media/BMP.php',
- 'DjVuHandler' => 'includes/media/DjVu.php',
- 'DjVuImage' => 'includes/media/DjVuImage.php',
- 'Exif' => 'includes/media/Exif.php',
- 'ExifBitmapHandler' => 'includes/media/ExifBitmap.php',
- 'FormatMetadata' => 'includes/media/FormatMetadata.php',
- 'GIFHandler' => 'includes/media/GIF.php',
- 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php',
- 'ImageHandler' => 'includes/media/ImageHandler.php',
- 'IPTC' => 'includes/media/IPTC.php',
- 'JpegHandler' => 'includes/media/Jpeg.php',
- 'JpegMetadataExtractor' => 'includes/media/JpegMetadataExtractor.php',
- 'MediaHandler' => 'includes/media/MediaHandler.php',
- 'MediaTransformError' => 'includes/media/MediaTransformOutput.php',
- 'MediaTransformOutput' => 'includes/media/MediaTransformOutput.php',
- 'PNGHandler' => 'includes/media/PNG.php',
- 'PNGMetadataExtractor' => 'includes/media/PNGMetadataExtractor.php',
- 'SvgHandler' => 'includes/media/SVG.php',
- 'SVGMetadataExtractor' => 'includes/media/SVGMetadataExtractor.php',
- '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',
- 'XMPReader' => 'includes/media/XMP.php',
- 'XMPValidate' => 'includes/media/XMPValidate.php',
-
- # includes/normal
- 'UtfNormal' => 'includes/normal/UtfNormal.php',
-
- # includes/objectcache
- 'APCBagOStuff' => 'includes/objectcache/APCBagOStuff.php',
- 'BagOStuff' => 'includes/objectcache/BagOStuff.php',
- 'EmptyBagOStuff' => 'includes/objectcache/EmptyBagOStuff.php',
- 'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php',
- 'MediaWikiBagOStuff' => 'includes/objectcache/SqlBagOStuff.php',
- 'MemCachedClientforWiki' => 'includes/objectcache/MemcachedClient.php',
- 'MemcachedBagOStuff' => 'includes/objectcache/MemcachedBagOStuff.php',
- 'MemcachedPeclBagOStuff' => 'includes/objectcache/MemcachedPeclBagOStuff.php',
- 'MemcachedPhpBagOStuff' => 'includes/objectcache/MemcachedPhpBagOStuff.php',
- 'MultiWriteBagOStuff' => 'includes/objectcache/MultiWriteBagOStuff.php',
- 'MWMemcached' => 'includes/objectcache/MemcachedClient.php',
- 'ObjectCache' => 'includes/objectcache/ObjectCache.php',
- 'ObjectCacheSessionHandler' => 'includes/objectcache/ObjectCacheSessionHandler.php',
- 'RedisBagOStuff' => 'includes/objectcache/RedisBagOStuff.php',
- 'SqlBagOStuff' => 'includes/objectcache/SqlBagOStuff.php',
- '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/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',
- 'PPDPart' => 'includes/parser/Preprocessor_DOM.php',
- 'PPDPart_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPDStack' => 'includes/parser/Preprocessor_DOM.php',
- 'PPDStackElement' => 'includes/parser/Preprocessor_DOM.php',
- 'PPDStackElement_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPDStack_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPFrame' => 'includes/parser/Preprocessor.php',
- 'PPFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
- 'PPFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'PPNode' => 'includes/parser/Preprocessor.php',
- 'PPNode_DOM' => 'includes/parser/Preprocessor_DOM.php',
- 'PPNode_Hash_Array' => 'includes/parser/Preprocessor_Hash.php',
- 'PPNode_Hash_Attr' => 'includes/parser/Preprocessor_Hash.php',
- 'PPNode_Hash_Text' => 'includes/parser/Preprocessor_Hash.php',
- 'PPNode_Hash_Tree' => 'includes/parser/Preprocessor_Hash.php',
- 'PPTemplateFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
- 'PPTemplateFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'Parser' => 'includes/parser/Parser.php',
- 'ParserCache' => 'includes/parser/ParserCache.php',
- 'ParserOptions' => 'includes/parser/ParserOptions.php',
- 'ParserOutput' => 'includes/parser/ParserOutput.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',
- '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',
- 'RedisPubSubFeedEngine' => 'includes/rcfeed/RedisPubSubFeedEngine.php',
- 'UDPRCFeedEngine' => 'includes/rcfeed/UDPRCFeedEngine.php',
- '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',
- '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',
- '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',
- 'ResourceLoaderLanguageNamesModule' =>
- 'includes/resourceloader/ResourceLoaderLanguageNamesModule.php',
- 'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php',
-
- # includes/revisiondelete
- '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
- 'SearchDatabase' => 'includes/search/SearchDatabase.php',
- 'SearchEngine' => 'includes/search/SearchEngine.php',
- 'SearchEngineDummy' => 'includes/search/SearchEngine.php',
- 'SearchHighlighter' => 'includes/search/SearchHighlighter.php',
- 'SearchMssql' => 'includes/search/SearchMssql.php',
- 'SearchMySQL' => 'includes/search/SearchMySQL.php',
- 'SearchNearMatchResultSet' => 'includes/search/SearchResultSet.php',
- 'SearchOracle' => 'includes/search/SearchOracle.php',
- 'SearchPostgres' => 'includes/search/SearchPostgres.php',
- 'SearchResult' => 'includes/search/SearchResult.php',
- 'SearchResultSet' => 'includes/search/SearchResultSet.php',
- 'SearchSqlite' => 'includes/search/SearchSqlite.php',
- 'SqlSearchResultSet' => 'includes/search/SearchResultSet.php',
-
- # includes/site
- 'MediaWikiSite' => 'includes/site/MediaWikiSite.php',
- 'Site' => 'includes/site/Site.php',
- 'SiteObject' => 'includes/site/Site.php',
- 'SiteArray' => 'includes/site/SiteList.php',
- 'SiteList' => 'includes/site/SiteList.php',
- 'SiteSQLStore' => 'includes/site/SiteSQLStore.php',
- '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',
- 'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php',
- 'BlockListPager' => 'includes/specials/SpecialBlockList.php',
- 'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php',
- 'CategoryPager' => 'includes/specials/SpecialCategories.php',
- 'ContribsPager' => 'includes/specials/SpecialContributions.php',
- 'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php',
- 'DeletedContribsPager' => 'includes/specials/SpecialDeletedContributions.php',
- 'DeletedContributionsPage' => 'includes/specials/SpecialDeletedContributions.php',
- 'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php',
- 'EditWatchlistCheckboxSeriesField' => 'includes/specials/SpecialEditWatchlist.php',
- 'EditWatchlistNormalHTMLForm' => 'includes/specials/SpecialEditWatchlist.php',
- 'EmailConfirmation' => 'includes/specials/SpecialConfirmemail.php',
- 'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php',
- 'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php',
- 'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php',
- 'ImageListPager' => 'includes/specials/SpecialListfiles.php',
- 'ImportReporter' => 'includes/specials/SpecialImport.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',
- 'MostimagesPage' => 'includes/specials/SpecialMostimages.php',
- 'MostinterwikisPage' => 'includes/specials/SpecialMostinterwikis.php',
- 'MostlinkedCategoriesPage' => 'includes/specials/SpecialMostlinkedcategories.php',
- 'MostlinkedPage' => 'includes/specials/SpecialMostlinked.php',
- 'MostlinkedTemplatesPage' => 'includes/specials/SpecialMostlinkedtemplates.php',
- 'MostrevisionsPage' => 'includes/specials/SpecialMostrevisions.php',
- 'MovePageForm' => 'includes/specials/SpecialMovepage.php',
- 'NewFilesPager' => 'includes/specials/SpecialNewimages.php',
- 'NewPagesPager' => 'includes/specials/SpecialNewpages.php',
- 'PageArchive' => 'includes/specials/SpecialUndelete.php',
- 'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php',
- 'ProtectedPagesPager' => 'includes/specials/SpecialProtectedpages.php',
- 'ProtectedTitlesPager' => 'includes/specials/SpecialProtectedtitles.php',
- 'RandomPage' => 'includes/specials/SpecialRandompage.php',
- 'ShortPagesPage' => 'includes/specials/SpecialShortpages.php',
- 'SpecialActiveUsers' => 'includes/specials/SpecialActiveusers.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',
- 'SpecialBookSources' => 'includes/specials/SpecialBooksources.php',
- 'SpecialCachedPage' => 'includes/specials/SpecialCachedPage.php',
- 'SpecialCategories' => 'includes/specials/SpecialCategories.php',
- 'SpecialChangeEmail' => 'includes/specials/SpecialChangeEmail.php',
- '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/specials/SpecialPermanentLink.php',
- 'SpecialPreferences' => 'includes/specials/SpecialPreferences.php',
- 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
- 'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php',
- 'SpecialProtectedtitles' => 'includes/specials/SpecialProtectedtitles.php',
- 'SpecialRandomInCategory' => 'includes/specials/SpecialRandomInCategory.php',
- 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php',
- 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.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',
- 'SpecialUpload' => 'includes/specials/SpecialUpload.php',
- 'SpecialUploadStash' => 'includes/specials/SpecialUploadStash.php',
- 'SpecialUploadStashTooLargeException' => 'includes/specials/SpecialUploadStash.php',
- 'SpecialUserlogout' => 'includes/specials/SpecialUserlogout.php',
- 'SpecialVersion' => 'includes/specials/SpecialVersion.php',
- 'SpecialWatchlist' => 'includes/specials/SpecialWatchlist.php',
- 'SpecialWhatLinksHere' => 'includes/specials/SpecialWhatlinkshere.php',
- 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php',
- 'UncategorizedImagesPage' => 'includes/specials/SpecialUncategorizedimages.php',
- 'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php',
- 'UncategorizedTemplatesPage' => 'includes/specials/SpecialUncategorizedtemplates.php',
- 'UnusedCategoriesPage' => 'includes/specials/SpecialUnusedcategories.php',
- 'UnusedimagesPage' => 'includes/specials/SpecialUnusedimages.php',
- 'UnusedtemplatesPage' => 'includes/specials/SpecialUnusedtemplates.php',
- 'UnwatchedpagesPage' => 'includes/specials/SpecialUnwatchedpages.php',
- 'UploadChunkFileException' => 'includes/upload/UploadFromChunks.php',
- 'UploadChunkZeroLengthFileException' => 'includes/upload/UploadFromChunks.php',
- 'UploadChunkVerificationException' => 'includes/upload/UploadFromChunks.php',
- 'UploadForm' => 'includes/specials/SpecialUpload.php',
- 'UploadSourceField' => 'includes/specials/SpecialUpload.php',
- 'UserrightsPage' => 'includes/specials/SpecialUserrights.php',
- 'UsersPager' => 'includes/specials/SpecialListusers.php',
- 'WantedCategoriesPage' => 'includes/specials/SpecialWantedcategories.php',
- 'WantedFilesPage' => 'includes/specials/SpecialWantedfiles.php',
- 'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php',
- 'WantedTemplatesPage' => 'includes/specials/SpecialWantedtemplates.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',
- 'UploadFromChunks' => 'includes/upload/UploadFromChunks.php',
- 'UploadFromStash' => 'includes/upload/UploadFromStash.php',
- 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
- 'UploadStash' => 'includes/upload/UploadStash.php',
- 'UploadStashBadPathException' => 'includes/upload/UploadStash.php',
- 'UploadStashException' => 'includes/upload/UploadStash.php',
- 'UploadStashFile' => 'includes/upload/UploadStash.php',
- 'UploadStashFileException' => 'includes/upload/UploadStash.php',
- 'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php',
- 'UploadStashNotAvailableException' => 'includes/upload/UploadStash.php',
- 'UploadStashZeroLengthFileException' => 'includes/upload/UploadStash.php',
- 'UploadStashNotLoggedInException' => 'includes/upload/UploadStash.php',
- '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/ConverterRule.php',
- 'FakeConverter' => 'languages/FakeConverter.php',
- 'Language' => 'languages/Language.php',
- 'LanguageConverter' => 'languages/LanguageConverter.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',
- 'CLDRPluralRuleEvaluatorRange' => 'languages/utils/CLDRPluralRuleEvaluatorRange.php',
- 'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleError.php',
-
- # maintenance
- 'BackupDumper' => 'maintenance/backup.inc',
- 'ConvertLinks' => 'maintenance/convertLinks.php',
- 'DeleteArchivedFilesImplementation' => 'maintenance/deleteArchivedFiles.inc',
- 'DeleteArchivedRevisionsImplementation' => 'maintenance/deleteArchivedRevisions.inc',
- 'DeleteDefaultMessages' => 'maintenance/deleteDefaultMessages.php',
- 'DumpDBZip2Output' => 'maintenance/backup.inc',
- 'ExportProgressFilter' => 'maintenance/backup.inc',
- 'FakeMaintenance' => 'maintenance/Maintenance.php',
- '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',
- 'PopulateLogSearch' => 'maintenance/populateLogSearch.php',
- 'PopulateLogUsertext' => 'maintenance/populateLogUsertext.php',
- 'PopulateParentId' => 'maintenance/populateParentId.php',
- 'PopulateRevisionLength' => 'maintenance/populateRevisionLength.php',
- 'PopulateRevisionSha1' => 'maintenance/populateRevisionSha1.php',
- 'RefreshLinks' => 'maintenance/refreshLinks.php',
- 'SevenZipStream' => 'maintenance/7zip.inc',
- 'Sqlite' => 'maintenance/sqlite.inc',
- 'UpdateCollation' => 'maintenance/updateCollation.php',
- 'UpdateRestrictions' => 'maintenance/updateRestrictions.php',
- 'UserDupes' => 'maintenance/userDupes.inc',
-
- # maintenance/language
- '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',
- 'DummyTermColorer' => 'maintenance/term/MWTerm.php',
-
- # mw-config
- 'InstallerOverrides' => 'mw-config/overrides.php',
- 'MyLocalSettingsGenerator' => 'mw-config/overrides.php',
-);
+require_once __DIR__ . '/../autoload.php';
class AutoLoader {
static protected $autoloadLocalClassesLower = null;
diff --git a/includes/Autopromote.php b/includes/Autopromote.php
index 81f3b7aa..d492d196 100644
--- a/includes/Autopromote.php
+++ b/includes/Autopromote.php
@@ -43,7 +43,7 @@ class Autopromote {
}
}
- wfRunHooks( 'GetAutoPromoteGroups', array( $user, &$promote ) );
+ Hooks::run( 'GetAutoPromoteGroups', array( $user, &$promote ) );
return $promote;
}
@@ -197,7 +197,7 @@ class Autopromote {
return in_array( 'bot', User::getGroupPermissions( $user->getGroups() ) );
default:
$result = null;
- wfRunHooks( 'AutopromoteCondition', array( $cond[0],
+ Hooks::run( '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 6a29a056..873a26d8 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -113,7 +113,7 @@ class Block {
$this->forcedTargetID = $user; // needed for foreign users
}
if ( $by ) { // local user
- $this->setBlocker( User::newFromID( $by ) );
+ $this->setBlocker( User::newFromId( $by ) );
} else { // foreign user
$this->setBlocker( $byText );
}
@@ -366,7 +366,7 @@ class Block {
protected function initFromRow( $row ) {
$this->setTarget( $row->ipb_address );
if ( $row->ipb_by ) { // local user
- $this->setBlocker( User::newFromID( $row->ipb_by ) );
+ $this->setBlocker( User::newFromId( $row->ipb_by ) );
} else { // foreign user
$this->setBlocker( $row->ipb_by_text );
}
@@ -442,19 +442,33 @@ class Block {
$dbw = wfGetDB( DB_MASTER );
}
- # Don't collide with expired blocks
- Block::purgeExpired();
+ # Periodic purge via commit hooks
+ if ( mt_rand( 0, 9 ) == 0 ) {
+ Block::purgeExpired();
+ }
$row = $this->getDatabaseArray();
$row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" );
- $dbw->insert(
- 'ipblocks',
- $row,
- __METHOD__,
- array( 'IGNORE' )
- );
+ $dbw->insert( 'ipblocks', $row, __METHOD__, array( 'IGNORE' ) );
$affected = $dbw->affectedRows();
+
+ # Don't collide with expired blocks.
+ # Do this after trying to insert to avoid pointless gap locks.
+ if ( !$affected ) {
+ $dbw->delete( 'ipblocks',
+ array(
+ 'ipb_address' => $row['ipb_address'],
+ 'ipb_user' => $row['ipb_user'],
+ 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() )
+ ),
+ __METHOD__
+ );
+
+ $dbw->insert( 'ipblocks', $row, __METHOD__, array( 'IGNORE' ) );
+ $affected = $dbw->affectedRows();
+ }
+
$this->mId = $dbw->insertId();
if ( $affected ) {
@@ -580,7 +594,7 @@ class Block {
if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) {
wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" );
- $continue = wfRunHooks(
+ $continue = Hooks::run(
'PerformRetroactiveAutoblock', array( $this, &$blockIds ) );
if ( $continue ) {
@@ -693,7 +707,7 @@ class Block {
}
# Allow hooks to cancel the autoblock.
- if ( !wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) {
+ if ( !Hooks::run( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) {
wfDebug( "Autoblock aborted by hook.\n" );
return false;
}
@@ -752,7 +766,6 @@ class Block {
* @return bool
*/
public function deleteIfExpired() {
- wfProfileIn( __METHOD__ );
if ( $this->isExpired() ) {
wfDebug( "Block::deleteIfExpired() -- deleting\n" );
@@ -763,7 +776,6 @@ class Block {
$retVal = false;
}
- wfProfileOut( __METHOD__ );
return $retVal;
}
@@ -885,7 +897,7 @@ class Block {
/**
* Get/set a flag determining whether the master is used for reads
*
- * @param bool $x
+ * @param bool|null $x
* @return bool
*/
public function fromMaster( $x = null ) {
@@ -893,8 +905,8 @@ class Block {
}
/**
- * Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range
- * @param bool $x
+ * Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range)
+ * @param bool|null $x
* @return bool
*/
public function isHardblock( $x = null ) {
@@ -906,6 +918,10 @@ class Block {
: $this->isHardblock;
}
+ /**
+ * @param null|bool $x
+ * @return bool
+ */
public function isAutoblocking( $x = null ) {
wfSetVar( $this->isAutoblocking, $x );
@@ -919,7 +935,7 @@ class Block {
/**
* Get/set whether the Block prevents a given action
* @param string $action
- * @param bool $x
+ * @param bool|null $x
* @return bool
*/
public function prevents( $action, $x = null ) {
@@ -1051,7 +1067,6 @@ class Block {
return array();
}
- wfProfileIn( __METHOD__ );
$conds = array();
foreach ( array_unique( $ipChain ) as $ipaddr ) {
# Discard invalid IP addresses. Since XFF can be spoofed and we do not
@@ -1073,7 +1088,6 @@ class Block {
}
if ( !count( $conds ) ) {
- wfProfileOut( __METHOD__ );
return array();
}
@@ -1104,12 +1118,12 @@ class Block {
}
}
- wfProfileOut( __METHOD__ );
return $blocks;
}
/**
* From a list of multiple blocks, find the most exact and strongest Block.
+ *
* The logic for finding the "best" block is:
* - Blocks that match the block's target IP are preferred over ones in a range
* - Hardblocks are chosen over softblocks that prevent account creation
@@ -1117,12 +1131,15 @@ 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
+ * This should be used when $blocks where retrieved from the user's IP address
+ * and $ipChain is populated from the same IP address information.
*
- * @param array $blocks Array of blocks
+ * @param array $blocks Array of Block objects
* @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, ...)
+ * @throws MWException
* @return Block|null The "best" block from the list
*/
public static function chooseBlock( array $blocks, array $ipChain ) {
@@ -1132,8 +1149,6 @@ class Block {
return $blocks[0];
}
- wfProfileIn( __METHOD__ );
-
// 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 ) {
@@ -1156,6 +1171,7 @@ class Block {
);
$ipChain = array_reverse( $ipChain );
+ /** @var Block $block */
foreach ( $blocks as $block ) {
// Stop searching if we have already have a "better" block. This
// is why the order of the blocks matters
@@ -1213,11 +1229,9 @@ class Block {
} elseif ( $blocksList['auto'] ) {
$chosenBlock = $blocksList['auto'];
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( "Proxy block found, but couldn't be classified." );
}
- wfProfileOut( __METHOD__ );
return $chosenBlock;
}
diff --git a/includes/Category.php b/includes/Category.php
index 322b0530..3a21e256 100644
--- a/includes/Category.php
+++ b/includes/Category.php
@@ -60,8 +60,6 @@ class Category {
return true;
}
- wfProfileIn( __METHOD__ );
-
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'category',
@@ -70,8 +68,6 @@ class Category {
__METHOD__
);
- wfProfileOut( __METHOD__ );
-
if ( !$row ) {
# Okay, there were no contents. Nothing to initialize.
if ( $this->mTitle ) {
@@ -258,7 +254,6 @@ class Category {
* @return TitleArray TitleArray object for category members.
*/
public function getMembers( $limit = false, $offset = '' ) {
- wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
@@ -284,8 +279,6 @@ class Category {
)
);
- wfProfileOut( __METHOD__ );
-
return $result;
}
@@ -318,8 +311,6 @@ class Category {
}
}
- wfProfileIn( __METHOD__ );
-
$dbw = wfGetDB( DB_MASTER );
$dbw->startAtomic( __METHOD__ );
@@ -363,8 +354,6 @@ class Category {
);
$dbw->endAtomic( __METHOD__ );
- wfProfileOut( __METHOD__ );
-
# Now we should update our local counts.
$this->mPages = $result->pages;
$this->mSubcats = $result->subcats;
diff --git a/includes/CategoryFinder.php b/includes/CategoryFinder.php
index cf537e15..33de7404 100644
--- a/includes/CategoryFinder.php
+++ b/includes/CategoryFinder.php
@@ -185,7 +185,6 @@ class CategoryFinder {
* Scans a "parent layer" of the articles/categories in $this->next
*/
private function scanNextLayer() {
- $profiler = new ProfileSection( __METHOD__ );
# Find all parents of the article currently in $this->next
$layer = array();
diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php
index 7581ae40..66079c01 100644
--- a/includes/CategoryViewer.php
+++ b/includes/CategoryViewer.php
@@ -89,6 +89,9 @@ class CategoryViewer extends ContextSource {
) {
$this->title = $title;
$this->setContext( $context );
+ $this->getOutput()->addModuleStyles( array(
+ 'mediawiki.action.view.categoryPage.styles'
+ ) );
$this->from = $from;
$this->until = $until;
$this->limit = $context->getConfig()->get( 'CategoryPagingLimit' );
@@ -104,7 +107,6 @@ class CategoryViewer extends ContextSource {
* @return string HTML output
*/
public function getHTML() {
- wfProfileIn( __METHOD__ );
$this->showGallery = $this->getConfig()->get( 'CategoryMagicGallery' )
&& !$this->getOutput()->mNoGallery;
@@ -136,11 +138,10 @@ class CategoryViewer extends ContextSource {
}
$lang = $this->getLanguage();
- $langAttribs = array( 'lang' => $lang->getCode(), 'dir' => $lang->getDir() );
+ $langAttribs = array( 'lang' => $lang->getHtmlCode(), 'dir' => $lang->getDir() );
# put a div around the headings which are in the user language
$r = Html::openElement( 'div', $langAttribs ) . $r . '</div>';
- wfProfileOut( __METHOD__ );
return $r;
}
@@ -154,7 +155,7 @@ class CategoryViewer extends ContextSource {
$mode = $this->getRequest()->getVal( 'gallerymode', null );
try {
$this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// User specified something invalid, fallback to default.
$this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
}
@@ -176,19 +177,30 @@ class CategoryViewer extends ContextSource {
// Subcategory; strip the 'Category' namespace from the link text.
$title = $cat->getTitle();
- $link = Linker::link( $title, htmlspecialchars( $title->getText() ) );
- if ( $title->isRedirect() ) {
- // This didn't used to add redirect-in-category, but might
- // as well be consistent with the rest of the sections
- // on a category page.
- $link = '<span class="redirect-in-category">' . $link . '</span>';
- }
- $this->children[] = $link;
+ $this->children[] = $this->generateLink(
+ 'subcat',
+ $title,
+ $title->isRedirect(),
+ htmlspecialchars( $title->getText() )
+ );
$this->children_start_char[] =
$this->getSubcategorySortChar( $cat->getTitle(), $sortkey );
}
+ function generateLink( $type, Title $title, $isRedirect, $html = null ) {
+ $link = null;
+ Hooks::run( 'CategoryViewer::generateLink', array( $type, $title, $html, &$link ) );
+ if ( $link === null ) {
+ $link = Linker::link( $title, $html );
+ }
+ if ( $isRedirect ) {
+ $link = '<span class="redirect-in-category">' . $link . '</span>';
+ }
+
+ return $link;
+ }
+
/**
* 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
@@ -231,13 +243,7 @@ class CategoryViewer extends ContextSource {
$this->gallery->add( $title );
}
} else {
- $link = Linker::link( $title );
- if ( $isRedirect ) {
- // This seems kind of pointless given 'mw-redirect' class,
- // but keeping for back-compatibility with user css.
- $link = '<span class="redirect-in-category">' . $link . '</span>';
- }
- $this->imgsNoGallery[] = $link;
+ $this->imgsNoGallery[] = $this->generateLink( 'image', $title, $isRedirect );
$this->imgsNoGallery_start_char[] = $wgContLang->convert(
$this->collation->getFirstLetter( $sortkey ) );
@@ -254,13 +260,7 @@ class CategoryViewer extends ContextSource {
function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
global $wgContLang;
- $link = Linker::link( $title );
- if ( $isRedirect ) {
- // This seems kind of pointless given 'mw-redirect' class,
- // but keeping for back-compatibility with user css.
- $link = '<span class="redirect-in-category">' . $link . '</span>';
- }
- $this->articles[] = $link;
+ $this->articles[] = $this->generateLink( 'page', $title, $isRedirect );
$this->articles_start_char[] = $wgContLang->convert(
$this->collation->getFirstLetter( $sortkey ) );
@@ -333,6 +333,8 @@ class CategoryViewer extends ContextSource {
)
);
+ Hooks::run( 'CategoryViewer::doCategoryQuery', array( $type, $res ) );
+
$count = 0;
foreach ( $res as $row ) {
$title = Title::newFromRow( $row );
@@ -390,7 +392,7 @@ class CategoryViewer extends ContextSource {
if ( $rescnt > 0 ) {
# Showing subcategories
$r .= "<div id=\"mw-subcategories\">\n";
- $r .= '<h2>' . $this->msg( 'subcategories' )->text() . "</h2>\n";
+ $r .= '<h2>' . $this->msg( 'subcategories' )->parse() . "</h2>\n";
$r .= $countmsg;
$r .= $this->getSectionPagingLinks( 'subcat' );
$r .= $this->formatList( $this->children, $this->children_start_char );
@@ -419,7 +421,7 @@ class CategoryViewer extends ContextSource {
if ( $rescnt > 0 ) {
$r = "<div id=\"mw-pages\">\n";
- $r .= '<h2>' . $this->msg( 'category_header', $ti )->text() . "</h2>\n";
+ $r .= '<h2>' . $this->msg( 'category_header', $ti )->parse() . "</h2>\n";
$r .= $countmsg;
$r .= $this->getSectionPagingLinks( 'page' );
$r .= $this->formatList( $this->articles, $this->articles_start_char );
@@ -515,7 +517,7 @@ class CategoryViewer extends ContextSource {
}
$pageLang = $this->title->getPageLanguage();
- $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
+ $attribs = array( 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
'class' => 'mw-content-' . $pageLang->getDir() );
$list = Html::rawElement( 'div', $attribs, $list );
@@ -529,8 +531,7 @@ class CategoryViewer extends ContextSource {
* TODO: Take the headers into account when creating columns, so they're
* more visually equal.
*
- * More distant TODO: Scrap this and use CSS columns, whenever IE finally
- * supports those.
+ * TODO: shortList and columnList are similar, need merging
*
* @param array $articles
* @param string[] $articles_start_char
@@ -539,50 +540,34 @@ class CategoryViewer extends ContextSource {
*/
static function columnList( $articles, $articles_start_char ) {
$columns = array_combine( $articles, $articles_start_char );
- # Split into three columns
- $columns = array_chunk( $columns, ceil( count( $columns ) / 3 ), true /* preserve keys */ );
- $ret = '<table style="width: 100%;"><tr style="vertical-align: top;">';
- $prevchar = null;
+ $ret = Html::openElement( 'div', array( 'class' => 'mw-category' ) );
- foreach ( $columns as $column ) {
- $ret .= '<td style="width: 33.3%;">';
- $colContents = array();
+ $colContents = array();
- # Kind of like array_flip() here, but we keep duplicates in an
- # array instead of dropping them.
- foreach ( $column as $article => $char ) {
- if ( !isset( $colContents[$char] ) ) {
- $colContents[$char] = array();
- }
- $colContents[$char][] = $article;
+ # Kind of like array_flip() here, but we keep duplicates in an
+ # array instead of dropping them.
+ foreach ( $columns as $article => $char ) {
+ if ( !isset( $colContents[$char] ) ) {
+ $colContents[$char] = array();
}
+ $colContents[$char][] = $article;
+ }
- $first = true;
- foreach ( $colContents as $char => $articles ) {
- # Change space to non-breaking space to keep headers aligned
- $h3char = $char === ' ' ? '&#160;' : htmlspecialchars( $char );
+ foreach ( $colContents as $char => $articles ) {
+ # Change space to non-breaking space to keep headers aligned
+ $h3char = $char === ' ' ? '&#160;' : htmlspecialchars( $char );
- $ret .= '<h3>' . $h3char;
- if ( $first && $char === $prevchar ) {
- # We're continuing a previous chunk at the top of a new
- # column, so add " cont." after the letter.
- $ret .= ' ' . wfMessage( 'listingcontinuesabbrev' )->escaped();
- }
- $ret .= "</h3>\n";
+ $ret .= '<div class="mw-category-group"><h3>' . $h3char;
+ $ret .= "</h3>\n";
- $ret .= '<ul><li>';
- $ret .= implode( "</li>\n<li>", $articles );
- $ret .= '</li></ul>';
-
- $first = false;
- $prevchar = $char;
- }
+ $ret .= '<ul><li>';
+ $ret .= implode( "</li>\n<li>", $articles );
+ $ret .= '</li></ul></div>';
- $ret .= "</td>\n";
}
- $ret .= '</tr></table>';
+ $ret .= Html::closeElement( 'div' );
return $ret;
}
@@ -618,7 +603,7 @@ class CategoryViewer extends ContextSource {
* @return string HTML
*/
private function pagingLinks( $first, $last, $type = '' ) {
- $prevLink = $this->msg( 'prevn' )->numParams( $this->limit )->escaped();
+ $prevLink = $this->msg( 'prev-page' )->text();
if ( $first != '' ) {
$prevQuery = $this->query;
@@ -632,7 +617,7 @@ class CategoryViewer extends ContextSource {
);
}
- $nextLink = $this->msg( 'nextn' )->numParams( $this->limit )->escaped();
+ $nextLink = $this->msg( 'next-page' )->text();
if ( $last != '' ) {
$lastQuery = $this->query;
diff --git a/includes/CdbCompat.php b/includes/CdbCompat.php
new file mode 100644
index 00000000..0074cc96
--- /dev/null
+++ b/includes/CdbCompat.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/***
+ * This file contains a set of backwards-compatability class names
+ * after the cdb functions were moved out into a separate library
+ * and put under a proper namespace
+ *
+ * @since 1.25
+ */
+
+/**
+ * @deprecated since 1.25
+ */
+abstract class CdbReader extends \Cdb\Reader {
+}
+
+/**
+ * @deprecated since 1.25
+ */
+abstract class CdbWriter extends \Cdb\Writer {
+}
+
+/**
+ * @deprecated since 1.25
+ */
+class CdbException extends \Cdb\Exception {
+}
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
index 94b7b7a9..09665dfb 100644
--- a/includes/ChangeTags.php
+++ b/includes/ChangeTags.php
@@ -18,10 +18,18 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
+ * @ingroup Change tagging
*/
class ChangeTags {
/**
+ * Can't delete tags with more than this many uses. Similar in intent to
+ * the bigdelete user right
+ * @todo Use the job queue for tag deletion to avoid this restriction
+ */
+ const MAX_DELETE_USES = 5000;
+
+ /**
* Creates HTML for the given tags
*
* @param string $tags Comma-separated list of tags
@@ -84,21 +92,50 @@ class ChangeTags {
*
* @throws MWException
* @return bool False if no changes are made, otherwise true
- *
- * @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
) {
- if ( !is_array( $tags ) ) {
- $tags = array( $tags );
- }
+ $result = self::updateTags( $tags, null, $rc_id, $rev_id, $log_id, $params );
+ return (bool)$result[0];
+ }
+
+ /**
+ * Add and remove tags to/from a change given its rc_id, rev_id and/or log_id,
+ * without verifying that the tags exist or are valid. If a tag is present in
+ * both $tagsToAdd and $tagsToRemove, it will be removed.
+ *
+ * This function should only be used by extensions to manipulate tags they
+ * have registered using the ListDefinedTags hook. When dealing with user
+ * input, call updateTagsWithChecks() instead.
+ *
+ * @param string|array|null $tagsToAdd Tags to add to the change
+ * @param string|array|null $tagsToRemove Tags to remove from the change
+ * @param int|null &$rc_id The rc_id of the change to add the tags to.
+ * Pass a variable whose value is null if the rc_id is not relevant or unknown.
+ * @param int|null &$rev_id The rev_id of the change to add the tags to.
+ * Pass a variable whose value is null if the rev_id is not relevant or unknown.
+ * @param int|null &$log_id The log_id of the change to add the tags to.
+ * Pass a variable whose value is null if the log_id is not relevant or unknown.
+ * @param string $params Params to put in the ct_params field of table
+ * 'change_tag' when adding tags
+ *
+ * @throws MWException When $rc_id, $rev_id and $log_id are all null
+ * @return array Index 0 is an array of tags actually added, index 1 is an
+ * array of tags actually removed, index 2 is an array of tags present on the
+ * revision or log entry before any changes were made
+ *
+ * @since 1.25
+ */
+ public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id = null,
+ &$rev_id = null, &$log_id = null, $params = null ) {
- $tags = array_filter( $tags ); // Make sure we're submitting all tags...
+ $tagsToAdd = array_filter( (array)$tagsToAdd ); // Make sure we're submitting all tags...
+ $tagsToRemove = array_filter( (array)$tagsToRemove );
if ( !$rc_id && !$rev_id && !$log_id ) {
throw new MWException( 'At least one of: RCID, revision ID, and log ID MUST be ' .
- 'specified when adding a tag to a change!' );
+ 'specified when adding or removing a tag from a change!' );
}
$dbw = wfGetDB( DB_MASTER );
@@ -137,11 +174,85 @@ class ChangeTags {
);
}
+ // update the tag_summary row
+ $prevTags = array();
+ if ( !self::updateTagSummaryRow( $tagsToAdd, $tagsToRemove, $rc_id, $rev_id,
+ $log_id, $prevTags ) ) {
+
+ // nothing to do
+ return array( array(), array(), $prevTags );
+ }
+
+ // insert a row into change_tag for each new tag
+ if ( count( $tagsToAdd ) ) {
+ $tagsRows = array();
+ foreach ( $tagsToAdd as $tag ) {
+ // Filter so we don't insert NULLs as zero accidentally.
+ // Keep in mind that $rc_id === null means "I don't care/know about the
+ // rc_id, just delete $tag on this revision/log entry". It doesn't
+ // mean "only delete tags on this revision/log WHERE rc_id IS NULL".
+ $tagsRows[] = array_filter(
+ array(
+ 'ct_tag' => $tag,
+ 'ct_rc_id' => $rc_id,
+ 'ct_log_id' => $log_id,
+ 'ct_rev_id' => $rev_id,
+ 'ct_params' => $params
+ )
+ );
+ }
+
+ $dbw->insert( 'change_tag', $tagsRows, __METHOD__, array( 'IGNORE' ) );
+ }
+
+ // delete from change_tag
+ if ( count( $tagsToRemove ) ) {
+ foreach ( $tagsToRemove as $tag ) {
+ $conds = array_filter(
+ array(
+ 'ct_tag' => $tag,
+ 'ct_rc_id' => $rc_id,
+ 'ct_log_id' => $log_id,
+ 'ct_rev_id' => $rev_id
+ )
+ );
+ $dbw->delete( 'change_tag', $conds, __METHOD__ );
+ }
+ }
+
+ self::purgeTagUsageCache();
+ return array( $tagsToAdd, $tagsToRemove, $prevTags );
+ }
+
+ /**
+ * Adds or removes a given set of tags to/from the relevant row of the
+ * tag_summary table. Modifies the tagsToAdd and tagsToRemove arrays to
+ * reflect the tags that were actually added and/or removed.
+ *
+ * @param array &$tagsToAdd
+ * @param array &$tagsToRemove If a tag is present in both $tagsToAdd and
+ * $tagsToRemove, it will be removed
+ * @param int|null $rc_id Null if not known or not applicable
+ * @param int|null $rev_id Null if not known or not applicable
+ * @param int|null $log_id Null if not known or not applicable
+ * @param array &$prevTags Optionally outputs a list of the tags that were
+ * in the tag_summary row to begin with
+ * @return bool True if any modifications were made, otherwise false
+ * @since 1.25
+ */
+ protected static function updateTagSummaryRow( &$tagsToAdd, &$tagsToRemove,
+ $rc_id, $rev_id, $log_id, &$prevTags = array() ) {
+
+ $dbw = wfGetDB( DB_MASTER );
+
$tsConds = array_filter( array(
'ts_rc_id' => $rc_id,
'ts_rev_id' => $rev_id,
- 'ts_log_id' => $log_id )
- );
+ 'ts_log_id' => $log_id
+ ) );
+
+ // Can't both add and remove a tag at the same time...
+ $tagsToAdd = array_diff( $tagsToAdd, $tagsToRemove );
// Update the summary row.
// $prevTags can be out of date on slaves, especially when addTags is called consecutively,
@@ -149,42 +260,277 @@ class ChangeTags {
$prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
$prevTags = $prevTags ? $prevTags : '';
$prevTags = array_filter( explode( ',', $prevTags ) );
- $newTags = array_unique( array_merge( $prevTags, $tags ) );
+
+ // add tags
+ $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
+ $newTags = array_unique( array_merge( $prevTags, $tagsToAdd ) );
+
+ // remove tags
+ $tagsToRemove = array_values( array_intersect( $tagsToRemove, $newTags ) );
+ $newTags = array_values( array_diff( $newTags, $tagsToRemove ) );
+
sort( $prevTags );
sort( $newTags );
-
if ( $prevTags == $newTags ) {
// No change.
return false;
}
- $dbw->replace(
- 'tag_summary',
- array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ),
- array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ),
- __METHOD__
- );
-
- // Insert the tags rows.
- $tagsRows = array();
- foreach ( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally.
- $tagsRows[] = array_filter(
- array(
- 'ct_tag' => $tag,
- 'ct_rc_id' => $rc_id,
- 'ct_log_id' => $log_id,
- 'ct_rev_id' => $rev_id,
- 'ct_params' => $params
- )
+ if ( !$newTags ) {
+ // no tags left, so delete the row altogether
+ $dbw->delete( 'tag_summary', $tsConds, __METHOD__ );
+ } else {
+ $dbw->replace( 'tag_summary',
+ array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ),
+ array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ),
+ __METHOD__
);
}
- $dbw->insert( 'change_tag', $tagsRows, __METHOD__, array( 'IGNORE' ) );
-
return true;
}
/**
+ * Helper function to generate a fatal status with a 'not-allowed' type error.
+ *
+ * @param string $msgOne Message key to use in the case of one tag
+ * @param string $msgMulti Message key to use in the case of more than one tag
+ * @param array $tags Restricted tags (passed as $1 into the message, count of
+ * $tags passed as $2)
+ * @return Status
+ * @since 1.25
+ */
+ protected static function restrictedTagError( $msgOne, $msgMulti, $tags ) {
+ $lang = RequestContext::getMain()->getLanguage();
+ $count = count( $tags );
+ return Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
+ $lang->commaList( $tags ), $count );
+ }
+
+ /**
+ * Is it OK to allow the user to apply all the specified tags at the same time
+ * as they edit/make the change?
+ *
+ * @param array $tags Tags that you are interested in applying
+ * @param User|null $user User whose permission you wish to check, or null if
+ * you don't care (e.g. maintenance scripts)
+ * @return Status
+ * @since 1.25
+ */
+ public static function canAddTagsAccompanyingChange( array $tags,
+ User $user = null ) {
+
+ if ( !is_null( $user ) && !$user->isAllowed( 'applychangetags' ) ) {
+ return Status::newFatal( 'tags-apply-no-permission' );
+ }
+
+ // to be applied, a tag has to be explicitly defined
+ // @todo Allow extensions to define tags that can be applied by users...
+ $allowedTags = self::listExplicitlyDefinedTags();
+ $disallowedTags = array_diff( $tags, $allowedTags );
+ if ( $disallowedTags ) {
+ return self::restrictedTagError( 'tags-apply-not-allowed-one',
+ 'tags-apply-not-allowed-multi', $disallowedTags );
+ }
+
+ return Status::newGood();
+ }
+
+ /**
+ * Adds tags to a given change, checking whether it is allowed first, but
+ * without adding a log entry. Useful for cases where the tag is being added
+ * along with the action that generated the change (e.g. tagging an edit as
+ * it is being made).
+ *
+ * Extensions should not use this function, unless directly handling a user
+ * request to add a particular tag. Normally, extensions should call
+ * ChangeTags::updateTags() instead.
+ *
+ * @param array $tags Tags to apply
+ * @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' when adding tags
+ * @param User $user Who to give credit for the action
+ * @return Status
+ * @since 1.25
+ */
+ public static function addTagsAccompanyingChangeWithChecks( array $tags,
+ $rc_id, $rev_id, $log_id, $params, User $user ) {
+
+ // are we allowed to do this?
+ $result = self::canAddTagsAccompanyingChange( $tags, $user );
+ if ( !$result->isOK() ) {
+ $result->value = null;
+ return $result;
+ }
+
+ // do it!
+ self::addTags( $tagsToAdd, $rc_id, $rev_id, $log_id, $params );
+
+ return Status::newGood( true );
+ }
+
+ /**
+ * Is it OK to allow the user to adds and remove the given tags tags to/from a
+ * change?
+ *
+ * @param array $tagsToAdd Tags that you are interested in adding
+ * @param array $tagsToRemove Tags that you are interested in removing
+ * @param User|null $user User whose permission you wish to check, or null if
+ * you don't care (e.g. maintenance scripts)
+ * @return Status
+ * @since 1.25
+ */
+ public static function canUpdateTags( array $tagsToAdd, array $tagsToRemove,
+ User $user = null ) {
+
+ if ( !is_null( $user ) && !$user->isAllowed( 'changetags' ) ) {
+ return Status::newFatal( 'tags-update-no-permission' );
+ }
+
+ // to be added, a tag has to be explicitly defined
+ // @todo Allow extensions to define tags that can be applied by users...
+ $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
+ $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
+ if ( $diff ) {
+ return self::restrictedTagError( 'tags-update-add-not-allowed-one',
+ 'tags-update-add-not-allowed-multi', $diff );
+ }
+
+ // to be removed, a tag has to be either explicitly defined or not defined
+ // at all
+ $definedTags = self::listDefinedTags();
+ $diff = array_diff( $tagsToRemove, $explicitlyDefinedTags );
+ if ( $diff ) {
+ $intersect = array_intersect( $diff, $definedTags );
+ if ( $intersect ) {
+ return self::restrictedTagError( 'tags-update-remove-not-allowed-one',
+ 'tags-update-remove-not-allowed-multi', $intersect );
+ }
+ }
+
+ return Status::newGood();
+ }
+
+ /**
+ * Adds and/or removes tags to/from a given change, checking whether it is
+ * allowed first, and adding a log entry afterwards.
+ *
+ * Includes a call to ChangeTag::canUpdateTags(), so your code doesn't need
+ * to do that. However, it doesn't check whether the *_id parameters are a
+ * valid combination. That is up to you to enforce. See ApiTag::execute() for
+ * an example.
+ *
+ * @param array|null $tagsToAdd If none, pass array() or null
+ * @param array|null $tagsToRemove If none, pass array() or null
+ * @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' when adding tags
+ * @param string $reason Comment for the log
+ * @param User $user Who to give credit for the action
+ * @return Status If successful, the value of this Status object will be an
+ * object (stdClass) with the following fields:
+ * - logId: the ID of the added log entry, or null if no log entry was added
+ * (i.e. no operation was performed)
+ * - addedTags: an array containing the tags that were actually added
+ * - removedTags: an array containing the tags that were actually removed
+ * @since 1.25
+ */
+ public static function updateTagsWithChecks( $tagsToAdd, $tagsToRemove,
+ $rc_id, $rev_id, $log_id, $params, $reason, User $user ) {
+
+ if ( is_null( $tagsToAdd ) ) {
+ $tagsToAdd = array();
+ }
+ if ( is_null( $tagsToRemove ) ) {
+ $tagsToRemove = array();
+ }
+ if ( !$tagsToAdd && !$tagsToRemove ) {
+ // no-op, don't bother
+ return Status::newGood( (object)array(
+ 'logId' => null,
+ 'addedTags' => array(),
+ 'removedTags' => array(),
+ ) );
+ }
+
+ // are we allowed to do this?
+ $result = self::canUpdateTags( $tagsToAdd, $tagsToRemove, $user );
+ if ( !$result->isOK() ) {
+ $result->value = null;
+ return $result;
+ }
+
+ // basic rate limiting
+ if ( $user->pingLimiter( 'changetag' ) ) {
+ return Status::newFatal( 'actionthrottledtext' );
+ }
+
+ // do it!
+ list( $tagsAdded, $tagsRemoved, $initialTags ) = self::updateTags( $tagsToAdd,
+ $tagsToRemove, $rc_id, $rev_id, $log_id, $params );
+ if ( !$tagsAdded && !$tagsRemoved ) {
+ // no-op, don't log it
+ return Status::newGood( (object)array(
+ 'logId' => null,
+ 'addedTags' => array(),
+ 'removedTags' => array(),
+ ) );
+ }
+
+ // log it
+ $logEntry = new ManualLogEntry( 'tag', 'update' );
+ $logEntry->setPerformer( $user );
+ $logEntry->setComment( $reason );
+
+ // find the appropriate target page
+ if ( $rev_id ) {
+ $rev = Revision::newFromId( $rev_id );
+ if ( $rev ) {
+ $title = $rev->getTitle();
+ $logEntry->setTarget( $rev->getTitle() );
+ }
+ } elseif ( $log_id ) {
+ // This function is from revision deletion logic and has nothing to do with
+ // change tags, but it appears to be the only other place in core where we
+ // perform logged actions on log items.
+ $logEntry->setTarget( RevDelLogList::suggestTarget( 0, array( $log_id ) ) );
+ }
+
+ if ( !$logEntry->getTarget() ) {
+ // target is required, so we have to set something
+ $logEntry->setTarget( SpecialPage::getTitleFor( 'Tags' ) );
+ }
+
+ $logParams = array(
+ '4::revid' => $rev_id,
+ '5::logid' => $log_id,
+ '6:list:tagsAdded' => $tagsAdded,
+ '7:number:tagsAddedCount' => count( $tagsAdded ),
+ '8:list:tagsRemoved' => $tagsRemoved,
+ '9:number:tagsRemovedCount' => count( $tagsRemoved ),
+ 'initialTags' => $initialTags,
+ );
+ $logEntry->setParameters( $logParams );
+ $logEntry->setRelations( array( 'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ) );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $logId = $logEntry->insert( $dbw );
+ // Only send this to UDP, not RC, similar to patrol events
+ $logEntry->publish( $logId, 'udp' );
+
+ return Status::newGood( (object)array(
+ 'logId' => $logId,
+ 'addedTags' => $tagsAdded,
+ 'removedTags' => $tagsRemoved,
+ ) );
+ }
+
+ /**
* Applies all tags-related changes to a query.
* Handles selecting tags, and filtering.
* Needs $tables to be set up properly, so we can figure out which join conditions to use.
@@ -265,7 +611,7 @@ class ChangeTags {
'tagfilter',
20,
$selected,
- array( 'class' => 'mw-tagfilter-input', 'id' => 'tagfilter' )
+ array( 'class' => 'mw-tagfilter-input mw-ui-input mw-ui-input-inline', 'id' => 'tagfilter' )
)
);
@@ -290,18 +636,451 @@ class ChangeTags {
}
/**
+ * Defines a tag in the valid_tag table, without checking that the tag name
+ * is valid.
+ * Extensions should NOT use this function; they can use the ListDefinedTags
+ * hook instead.
+ *
+ * @param string $tag Tag to create
+ * @since 1.25
+ */
+ public static function defineTag( $tag ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->replace( 'valid_tag',
+ array( 'vt_tag' ),
+ array( 'vt_tag' => $tag ),
+ __METHOD__ );
+
+ // clear the memcache of defined tags
+ self::purgeTagCacheAll();
+ }
+
+ /**
+ * Removes a tag from the valid_tag table. The tag may remain in use by
+ * extensions, and may still show up as 'defined' if an extension is setting
+ * it from the ListDefinedTags hook.
+ *
+ * @param string $tag Tag to remove
+ * @since 1.25
+ */
+ public static function undefineTag( $tag ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'valid_tag', array( 'vt_tag' => $tag ), __METHOD__ );
+
+ // clear the memcache of defined tags
+ self::purgeTagCacheAll();
+ }
+
+ /**
+ * Writes a tag action into the tag management log.
+ *
+ * @param string $action
+ * @param string $tag
+ * @param string $reason
+ * @param User $user Who to attribute the action to
+ * @param int $tagCount For deletion only, how many usages the tag had before
+ * it was deleted.
+ * @since 1.25
+ */
+ protected static function logTagManagementAction( $action, $tag, $reason,
+ User $user, $tagCount = null ) {
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $logEntry = new ManualLogEntry( 'managetags', $action );
+ $logEntry->setPerformer( $user );
+ // target page is not relevant, but it has to be set, so we just put in
+ // the title of Special:Tags
+ $logEntry->setTarget( Title::newFromText( 'Special:Tags' ) );
+ $logEntry->setComment( $reason );
+
+ $params = array( '4::tag' => $tag );
+ if ( !is_null( $tagCount ) ) {
+ $params['5:number:count'] = $tagCount;
+ }
+ $logEntry->setParameters( $params );
+ $logEntry->setRelations( array( 'Tag' => $tag ) );
+
+ $logId = $logEntry->insert( $dbw );
+ $logEntry->publish( $logId );
+ return $logId;
+ }
+
+ /**
+ * Is it OK to allow the user to activate this tag?
+ *
+ * @param string $tag Tag that you are interested in activating
+ * @param User|null $user User whose permission you wish to check, or null if
+ * you don't care (e.g. maintenance scripts)
+ * @return Status
+ * @since 1.25
+ */
+ public static function canActivateTag( $tag, User $user = null ) {
+ if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
+ return Status::newFatal( 'tags-manage-no-permission' );
+ }
+
+ // non-existing tags cannot be activated
+ $tagUsage = self::tagUsageStatistics();
+ if ( !isset( $tagUsage[$tag] ) ) {
+ return Status::newFatal( 'tags-activate-not-found', $tag );
+ }
+
+ // defined tags cannot be activated (a defined tag is either extension-
+ // defined, in which case the extension chooses whether or not to active it;
+ // or user-defined, in which case it is considered active)
+ $definedTags = self::listDefinedTags();
+ if ( in_array( $tag, $definedTags ) ) {
+ return Status::newFatal( 'tags-activate-not-allowed', $tag );
+ }
+
+ return Status::newGood();
+ }
+
+ /**
+ * Activates a tag, checking whether it is allowed first, and adding a log
+ * entry afterwards.
+ *
+ * Includes a call to ChangeTag::canActivateTag(), so your code doesn't need
+ * to do that.
+ *
+ * @param string $tag
+ * @param string $reason
+ * @param User $user Who to give credit for the action
+ * @param bool $ignoreWarnings Can be used for API interaction, default false
+ * @return Status If successful, the Status contains the ID of the added log
+ * entry as its value
+ * @since 1.25
+ */
+ public static function activateTagWithChecks( $tag, $reason, User $user,
+ $ignoreWarnings = false ) {
+
+ // are we allowed to do this?
+ $result = self::canActivateTag( $tag, $user );
+ if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
+ $result->value = null;
+ return $result;
+ }
+
+ // do it!
+ self::defineTag( $tag );
+
+ // log it
+ $logId = self::logTagManagementAction( 'activate', $tag, $reason, $user );
+ return Status::newGood( $logId );
+ }
+
+ /**
+ * Is it OK to allow the user to deactivate this tag?
+ *
+ * @param string $tag Tag that you are interested in deactivating
+ * @param User|null $user User whose permission you wish to check, or null if
+ * you don't care (e.g. maintenance scripts)
+ * @return Status
+ * @since 1.25
+ */
+ public static function canDeactivateTag( $tag, User $user = null ) {
+ if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
+ return Status::newFatal( 'tags-manage-no-permission' );
+ }
+
+ // only explicitly-defined tags can be deactivated
+ $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
+ if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
+ return Status::newFatal( 'tags-deactivate-not-allowed', $tag );
+ }
+ return Status::newGood();
+ }
+
+ /**
+ * Deactivates a tag, checking whether it is allowed first, and adding a log
+ * entry afterwards.
+ *
+ * Includes a call to ChangeTag::canDeactivateTag(), so your code doesn't need
+ * to do that.
+ *
+ * @param string $tag
+ * @param string $reason
+ * @param User $user Who to give credit for the action
+ * @param bool $ignoreWarnings Can be used for API interaction, default false
+ * @return Status If successful, the Status contains the ID of the added log
+ * entry as its value
+ * @since 1.25
+ */
+ public static function deactivateTagWithChecks( $tag, $reason, User $user,
+ $ignoreWarnings = false ) {
+
+ // are we allowed to do this?
+ $result = self::canDeactivateTag( $tag, $user );
+ if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
+ $result->value = null;
+ return $result;
+ }
+
+ // do it!
+ self::undefineTag( $tag );
+
+ // log it
+ $logId = self::logTagManagementAction( 'deactivate', $tag, $reason, $user );
+ return Status::newGood( $logId );
+ }
+
+ /**
+ * Is it OK to allow the user to create this tag?
+ *
+ * @param string $tag Tag that you are interested in creating
+ * @param User|null $user User whose permission you wish to check, or null if
+ * you don't care (e.g. maintenance scripts)
+ * @return Status
+ * @since 1.25
+ */
+ public static function canCreateTag( $tag, User $user = null ) {
+ if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
+ return Status::newFatal( 'tags-manage-no-permission' );
+ }
+
+ // no empty tags
+ if ( $tag === '' ) {
+ return Status::newFatal( 'tags-create-no-name' );
+ }
+
+ // tags cannot contain commas (used as a delimiter in tag_summary table) or
+ // slashes (would break tag description messages in MediaWiki namespace)
+ if ( strpos( $tag, ',' ) !== false || strpos( $tag, '/' ) !== false ) {
+ return Status::newFatal( 'tags-create-invalid-chars' );
+ }
+
+ // could the MediaWiki namespace description messages be created?
+ $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Tag-$tag-description" );
+ if ( is_null( $title ) ) {
+ return Status::newFatal( 'tags-create-invalid-title-chars' );
+ }
+
+ // does the tag already exist?
+ $tagUsage = self::tagUsageStatistics();
+ if ( isset( $tagUsage[$tag] ) ) {
+ return Status::newFatal( 'tags-create-already-exists', $tag );
+ }
+
+ // check with hooks
+ $canCreateResult = Status::newGood();
+ Hooks::run( 'ChangeTagCanCreate', array( $tag, $user, &$canCreateResult ) );
+ return $canCreateResult;
+ }
+
+ /**
+ * Creates a tag by adding a row to the `valid_tag` table.
+ *
+ * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to
+ * do that.
+ *
+ * @param string $tag
+ * @param string $reason
+ * @param User $user Who to give credit for the action
+ * @param bool $ignoreWarnings Can be used for API interaction, default false
+ * @return Status If successful, the Status contains the ID of the added log
+ * entry as its value
+ * @since 1.25
+ */
+ public static function createTagWithChecks( $tag, $reason, User $user,
+ $ignoreWarnings = false ) {
+
+ // are we allowed to do this?
+ $result = self::canCreateTag( $tag, $user );
+ if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
+ $result->value = null;
+ return $result;
+ }
+
+ // do it!
+ self::defineTag( $tag );
+
+ // log it
+ $logId = self::logTagManagementAction( 'create', $tag, $reason, $user );
+ return Status::newGood( $logId );
+ }
+
+ /**
+ * Permanently removes all traces of a tag from the DB. Good for removing
+ * misspelt or temporary tags.
+ *
+ * This function should be directly called by maintenance scripts only, never
+ * by user-facing code. See deleteTagWithChecks() for functionality that can
+ * safely be exposed to users.
+ *
+ * @param string $tag Tag to remove
+ * @return Status The returned status will be good unless a hook changed it
+ * @since 1.25
+ */
+ public static function deleteTagEverywhere( $tag ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin( __METHOD__ );
+
+ // delete from valid_tag
+ self::undefineTag( $tag );
+
+ // find out which revisions use this tag, so we can delete from tag_summary
+ $result = $dbw->select( 'change_tag',
+ array( 'ct_rc_id', 'ct_log_id', 'ct_rev_id', 'ct_tag' ),
+ array( 'ct_tag' => $tag ),
+ __METHOD__ );
+ foreach ( $result as $row ) {
+ // remove the tag from the relevant row of tag_summary
+ $tagsToAdd = array();
+ $tagsToRemove = array( $tag );
+ self::updateTagSummaryRow( $tagsToAdd, $tagsToRemove, $row->ct_rc_id,
+ $row->ct_rev_id, $row->ct_log_id );
+ }
+
+ // delete from change_tag
+ $dbw->delete( 'change_tag', array( 'ct_tag' => $tag ), __METHOD__ );
+
+ $dbw->commit( __METHOD__ );
+
+ // give extensions a chance
+ $status = Status::newGood();
+ Hooks::run( 'ChangeTagAfterDelete', array( $tag, &$status ) );
+ // let's not allow error results, as the actual tag deletion succeeded
+ if ( !$status->isOK() ) {
+ wfDebug( 'ChangeTagAfterDelete error condition downgraded to warning' );
+ $status->ok = true;
+ }
+
+ // clear the memcache of defined tags
+ self::purgeTagCacheAll();
+
+ return $status;
+ }
+
+ /**
+ * Is it OK to allow the user to delete this tag?
+ *
+ * @param string $tag Tag that you are interested in deleting
+ * @param User|null $user User whose permission you wish to check, or null if
+ * you don't care (e.g. maintenance scripts)
+ * @return Status
+ * @since 1.25
+ */
+ public static function canDeleteTag( $tag, User $user = null ) {
+ $tagUsage = self::tagUsageStatistics();
+
+ if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
+ return Status::newFatal( 'tags-manage-no-permission' );
+ }
+
+ if ( !isset( $tagUsage[$tag] ) ) {
+ return Status::newFatal( 'tags-delete-not-found', $tag );
+ }
+
+ if ( $tagUsage[$tag] > self::MAX_DELETE_USES ) {
+ return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
+ }
+
+ $extensionDefined = self::listExtensionDefinedTags();
+ if ( in_array( $tag, $extensionDefined ) ) {
+ // extension-defined tags can't be deleted unless the extension
+ // specifically allows it
+ $status = Status::newFatal( 'tags-delete-not-allowed' );
+ } else {
+ // user-defined tags are deletable unless otherwise specified
+ $status = Status::newGood();
+ }
+
+ Hooks::run( 'ChangeTagCanDelete', array( $tag, $user, &$status ) );
+ return $status;
+ }
+
+ /**
+ * Deletes a tag, checking whether it is allowed first, and adding a log entry
+ * afterwards.
+ *
+ * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to
+ * do that.
+ *
+ * @param string $tag
+ * @param string $reason
+ * @param User $user Who to give credit for the action
+ * @param bool $ignoreWarnings Can be used for API interaction, default false
+ * @return Status If successful, the Status contains the ID of the added log
+ * entry as its value
+ * @since 1.25
+ */
+ public static function deleteTagWithChecks( $tag, $reason, User $user,
+ $ignoreWarnings = false ) {
+
+ // are we allowed to do this?
+ $result = self::canDeleteTag( $tag, $user );
+ if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
+ $result->value = null;
+ return $result;
+ }
+
+ // store the tag usage statistics
+ $tagUsage = self::tagUsageStatistics();
+
+ // do it!
+ $deleteResult = self::deleteTagEverywhere( $tag );
+ if ( !$deleteResult->isOK() ) {
+ return $deleteResult;
+ }
+
+ // log it
+ $logId = self::logTagManagementAction( 'delete', $tag, $reason, $user, $tagUsage[$tag] );
+ $deleteResult->value = $logId;
+ return $deleteResult;
+ }
+
+ /**
+ * Lists those tags which extensions report as being "active".
+ *
+ * @return array
+ * @since 1.25
+ */
+ public static function listExtensionActivatedTags() {
+ // Caching...
+ global $wgMemc;
+ $key = wfMemcKey( 'active-tags' );
+ $tags = $wgMemc->get( $key );
+ if ( $tags ) {
+ return $tags;
+ }
+
+ // ask extensions which tags they consider active
+ $extensionActive = array();
+ Hooks::run( 'ChangeTagsListActive', array( &$extensionActive ) );
+
+ // Short-term caching.
+ $wgMemc->set( $key, $extensionActive, 300 );
+ return $extensionActive;
+ }
+
+ /**
* Basically lists defined tags which count even if they aren't applied to anything.
- * Tags on items in table 'change_tag' which are not (or no longer) in table 'valid_tag'
- * are not included.
+ * It returns a union of the results of listExplicitlyDefinedTags() and
+ * listExtensionDefinedTags().
+ *
+ * @return string[] Array of strings: tags
+ */
+ public static function listDefinedTags() {
+ $tags1 = self::listExplicitlyDefinedTags();
+ $tags2 = self::listExtensionDefinedTags();
+ return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
+ }
+
+ /**
+ * Lists tags explicitly defined in the `valid_tag` table of the database.
+ * Tags in table 'change_tag' which are not in table 'valid_tag' are not
+ * included.
*
* Tries memcached first.
*
* @return string[] Array of strings: tags
+ * @since 1.25
*/
- public static function listDefinedTags() {
+ public static function listExplicitlyDefinedTags() {
// Caching...
global $wgMemc;
- $key = wfMemcKey( 'valid-tags' );
+ $key = wfMemcKey( 'valid-tags-db' );
$tags = $wgMemc->get( $key );
if ( $tags ) {
return $tags;
@@ -316,8 +1095,33 @@ class ChangeTags {
$emptyTags[] = $row->vt_tag;
}
- wfRunHooks( 'ListDefinedTags', array( &$emptyTags ) );
+ $emptyTags = array_filter( array_unique( $emptyTags ) );
+ // Short-term caching.
+ $wgMemc->set( $key, $emptyTags, 300 );
+ return $emptyTags;
+ }
+
+ /**
+ * Lists tags defined by extensions using the ListDefinedTags hook.
+ * Extensions need only define those tags they deem to be in active use.
+ *
+ * Tries memcached first.
+ *
+ * @return string[] Array of strings: tags
+ * @since 1.25
+ */
+ public static function listExtensionDefinedTags() {
+ // Caching...
+ global $wgMemc;
+ $key = wfMemcKey( 'valid-tags-hook' );
+ $tags = $wgMemc->get( $key );
+ if ( $tags ) {
+ return $tags;
+ }
+
+ $emptyTags = array();
+ Hooks::run( 'ListDefinedTags', array( &$emptyTags ) );
$emptyTags = array_filter( array_unique( $emptyTags ) );
// Short-term caching.
@@ -326,12 +1130,45 @@ class ChangeTags {
}
/**
+ * Invalidates the short-term cache of defined tags used by the
+ * list*DefinedTags functions, as well as the tag statistics cache.
+ * @since 1.25
+ */
+ public static function purgeTagCacheAll() {
+ global $wgMemc;
+ $wgMemc->delete( wfMemcKey( 'active-tags' ) );
+ $wgMemc->delete( wfMemcKey( 'valid-tags-db' ) );
+ $wgMemc->delete( wfMemcKey( 'valid-tags-hook' ) );
+ self::purgeTagUsageCache();
+ }
+
+ /**
+ * Invalidates the tag statistics cache only.
+ * @since 1.25
+ */
+ public static function purgeTagUsageCache() {
+ global $wgMemc;
+ $wgMemc->delete( wfMemcKey( 'change-tag-statistics' ) );
+ }
+
+ /**
* Returns a map of any tags used on the wiki to number of edits
* tagged with them, ordered descending by the hitcount.
*
+ * Keeps a short-term cache in memory, so calling this multiple times in the
+ * same request should be fine.
+ *
* @return array Array of string => int
*/
public static function tagUsageStatistics() {
+ // Caching...
+ global $wgMemc;
+ $key = wfMemcKey( 'change-tag-statistics' );
+ $stats = $wgMemc->get( $key );
+ if ( $stats ) {
+ return $stats;
+ }
+
$out = array();
$dbr = wfGetDB( DB_SLAVE );
@@ -352,6 +1189,26 @@ class ChangeTags {
}
}
+ // Cache for a very short time
+ $wgMemc->set( $key, $out, 300 );
return $out;
}
+
+ /**
+ * Indicate whether change tag editing UI is relevant
+ *
+ * Returns true if the user has the necessary right and there are any
+ * editable tags defined.
+ *
+ * This intentionally doesn't check "any addable || any deletable", because
+ * it seems like it would be more confusing than useful if the checkboxes
+ * suddenly showed up because some abuse filter stopped defining a tag and
+ * then suddenly disappeared when someone deleted all uses of that tag.
+ *
+ * @param User $user
+ * @return bool
+ */
+ public static function showTagEditingUI( User $user ) {
+ return $user->isAllowed( 'changetags' ) && (bool)self::listExplicitlyDefinedTags();
+ }
}
diff --git a/includes/Collation.php b/includes/Collation.php
index 1c2c2db3..481d8e70 100644
--- a/includes/Collation.php
+++ b/includes/Collation.php
@@ -59,7 +59,7 @@ abstract class Collation {
# Provide a mechanism for extensions to hook in.
$collationObject = null;
- wfRunHooks( 'Collation::factory', array( $collationName, &$collationObject ) );
+ Hooks::run( 'Collation::factory', array( $collationName, &$collationObject ) );
if ( $collationObject instanceof Collation ) {
return $collationObject;
@@ -341,7 +341,7 @@ 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( UtfNormal\Utils::utf8ToCodepoint( $firstChar ) ) ) {
return $firstChar;
}
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index aad42aac..010c471c 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -52,6 +52,8 @@ if ( !defined( 'MEDIAWIKI' ) ) {
die( 1 );
}
+/** @endcond */
+
/**
* wgConf hold the site configuration.
* Not used for much in a default install.
@@ -71,11 +73,9 @@ $wgConfigRegistry = array(
/**
* 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.24.2';
+$wgVersion = '1.25.1';
/**
* Name of the site. It must be changed in LocalSettings.php
@@ -154,12 +154,15 @@ $wgUsePathInfo = ( strpos( PHP_SAPI, 'cgi' ) === false ) &&
( strpos( PHP_SAPI, 'isapi' ) === false );
/**
- * The extension to append to script names by default. This can either be .php
- * or .php5.
+ * The extension to append to script names by default.
+ *
+ * Some hosting providers used PHP 4 for *.php files, and PHP 5 for *.php5.
+ * This variable was provided to support those providers.
*
- * Some hosting providers use PHP 4 for *.php files, and PHP 5 for *.php5. This
- * variable is provided to support those providers.
* @since 1.11
+ * @deprecated since 1.25; support for '.php5' is being phased out of MediaWiki
+ * proper. Backward-compatibility can be maintained by configuring your web
+ * server to rewrite URLs. See RELEASE-NOTES for details.
*/
$wgScriptExtension = '.php';
@@ -221,11 +224,18 @@ $wgLocalStylePath = false;
$wgExtensionAssetsPath = false;
/**
+ * Filesystem extensions directory.
+ * Defaults to "{$IP}/extensions".
+ * @since 1.25
+ */
+$wgExtensionDirectory = "{$IP}/extensions";
+
+/**
* Filesystem stylesheets directory.
* Defaults to "{$IP}/skins".
* @since 1.3
*/
-$wgStyleDirectory = false;
+$wgStyleDirectory = "{$IP}/skins";
/**
* The URL path for primary article page views. This path should contain $1,
@@ -260,6 +270,23 @@ $wgFileCacheDirectory = false;
$wgLogo = false;
/**
+ * Array with URL paths to HD versions of the wiki logo. The scaled logo size
+ * should be under 135x155 pixels.
+ * Only 1.5x and 2x versions are supported.
+ *
+ * @par Example:
+ * @code
+ * $wgLogoHD = array(
+ * "1.5x" => "path/to/1.5x_version.png",
+ * "2x" => "path/to/2x_version.png"
+ * );
+ * @endcode
+ *
+ * @since 1.25
+ */
+$wgLogoHD = false;
+
+/**
* The URL path of the shortcut icon.
* @since 1.6
*/
@@ -273,6 +300,16 @@ $wgFavicon = '/favicon.ico';
$wgAppleTouchIcon = false;
/**
+ * Value for the referrer policy meta tag.
+ * One of 'never', 'default', 'origin', 'always'. Setting it to false just
+ * prevents the meta tag from being output.
+ * See http://www.w3.org/TR/referrer-policy/ for details.
+ *
+ * @since 1.25
+ */
+$wgReferrerPolicy = false;
+
+/**
* The local filesystem path to a temporary directory. This is not required to
* be web accessible.
*
@@ -943,12 +980,13 @@ $wgExiv2Command = '/usr/bin/exiv2';
* are passed as parameters after $srcPath, $dstPath, $width, $height
*/
$wgSVGConverters = array(
- 'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output',
+ 'ImageMagick' =>
+ '$path/convert -background "#ffffff00" -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',
- 'rsvg' => '$path/rsvg -w $width -h $height $input $output',
+ 'rsvg' => '$path/rsvg-convert -w $width -h $height -o $output $input',
'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output',
'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ),
);
@@ -1245,6 +1283,46 @@ $wgThumbnailBuckets = null;
$wgThumbnailMinimumBucketDistance = 50;
/**
+ * When defined, is an array of thumbnail widths to be rendered at upload time. The idea is to
+ * prerender common thumbnail sizes, in order to avoid the necessity to render them on demand, which
+ * has a performance impact for the first client to view a certain size.
+ *
+ * This obviously means that more disk space is needed per upload upfront.
+ *
+ * @since 1.25
+ */
+
+$wgUploadThumbnailRenderMap = array();
+
+/**
+ * The method through which the thumbnails will be prerendered for the entries in
+ * $wgUploadThumbnailRenderMap
+ *
+ * The method can be either "http" or "jobqueue". The former uses an http request to hit the
+ * thumbnail's URL.
+ * This method only works if thumbnails are configured to be rendered by a 404 handler. The latter
+ * option uses the job queue to render the thumbnail.
+ *
+ * @since 1.25
+ */
+$wgUploadThumbnailRenderMethod = 'jobqueue';
+
+/**
+ * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom Host HTTP header.
+ *
+ * @since 1.25
+ */
+$wgUploadThumbnailRenderHttpCustomHost = false;
+
+/**
+ * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom domain to send the
+ * HTTP request to.
+ *
+ * @since 1.25
+ */
+$wgUploadThumbnailRenderHttpCustomDomain = false;
+
+/**
* Default parameters for the "<gallery>" tag
*/
$wgGalleryOptions = array(
@@ -1272,9 +1350,11 @@ $wgDirectoryMode = 0777;
* Generate and use thumbnails suitable for screens with 1.5 and 2.0 pixel densities.
*
* This means a 320x240 use of an image on the wiki will also generate 480x360 and 640x480
- * thumbnails, output via data-src-1-5 and data-src-2-0. Runtime JavaScript switches the
- * images in after loading the original low-resolution versions depending on the reported
- * window.devicePixelRatio.
+ * thumbnails, output via the srcset attribute.
+ *
+ * On older browsers, a JavaScript polyfill switches the appropriate images in after loading
+ * the original low-resolution versions depending on the reported window.devicePixelRatio.
+ * The polyfill can be found in the jquery.hidpi module.
*/
$wgResponsiveImages = true;
@@ -1345,7 +1425,7 @@ $wgDjvuOutputExtension = 'jpg';
/**
* Site admin email address.
*
- * Defaults to "wikiadmin@{$wgServerName}".
+ * Defaults to "wikiadmin@$wgServerName".
*/
$wgEmergencyContact = false;
@@ -1354,7 +1434,7 @@ $wgEmergencyContact = false;
*
* The address we should use as sender when a user is requesting his password.
*
- * Defaults to "apache@{$wgServerName}".
+ * Defaults to "apache@$wgServerName".
*/
$wgPasswordSender = false;
@@ -1664,6 +1744,9 @@ $wgAllDBsAreLocalhost = false;
* $wgSharedPrefix is the table prefix for the shared database. It defaults to
* $wgDBprefix.
*
+ * $wgSharedSchema is the table schema for the shared database. It defaults to
+ * $wgDBmwschema.
+ *
* @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
@@ -1682,6 +1765,12 @@ $wgSharedPrefix = false;
$wgSharedTables = array( 'user', 'user_properties' );
/**
+ * @see $wgSharedDB
+ * @since 1.23
+ */
+$wgSharedSchema = false;
+
+/**
* Database load balancer
* This is a two-dimensional array, an array of server info structures
* Fields are:
@@ -1960,15 +2049,6 @@ $wgAllowSlowParserFunctions = false;
$wgAllowSchemaUpdates = true;
/**
- * Anti-lock flags - bitfield
- * - ALF_NO_LINK_LOCK:
- * Don't use locking reads when updating the link table. This is
- * necessary for wikis with a high edit rate for performance
- * reasons, but may cause link table inconsistency
- */
-$wgAntiLockFlags = 0;
-
-/**
* Maximum article size in kilobytes
*/
$wgMaxArticleSize = 2048;
@@ -2063,43 +2143,21 @@ $wgLanguageConverterCacheType = CACHE_ANYTHING;
*/
$wgObjectCaches = array(
CACHE_NONE => array( 'class' => 'EmptyBagOStuff' ),
- CACHE_DB => array( 'class' => 'SqlBagOStuff', 'table' => 'objectcache' ),
+ CACHE_DB => array( 'class' => 'SqlBagOStuff', 'loggroup' => 'SQLBagOStuff' ),
CACHE_ANYTHING => array( 'factory' => 'ObjectCache::newAnything' ),
CACHE_ACCEL => array( 'factory' => 'ObjectCache::newAccelerator' ),
- CACHE_MEMCACHED => array( 'factory' => 'ObjectCache::newMemcached' ),
+ CACHE_MEMCACHED => array( 'factory' => 'ObjectCache::newMemcached', 'loggroup' => 'memcached' ),
'apc' => array( 'class' => 'APCBagOStuff' ),
'xcache' => array( 'class' => 'XCacheBagOStuff' ),
'wincache' => array( 'class' => 'WinCacheBagOStuff' ),
- 'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff' ),
- 'memcached-pecl' => array( 'class' => 'MemcachedPeclBagOStuff' ),
+ 'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff', 'loggroup' => 'memcached' ),
+ 'memcached-pecl' => array( 'class' => 'MemcachedPeclBagOStuff', 'loggroup' => 'memcached' ),
'hash' => array( 'class' => 'HashBagOStuff' ),
);
/**
- * 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
- */
-$wgBloomFilterStores = array();
-
-/**
* The expiry time for the parser cache, in seconds.
* The default is 86400 (one day).
*/
@@ -2310,6 +2368,23 @@ $wgClockSkewFudge = 5;
*/
$wgInvalidateCacheOnLocalSettingsChange = true;
+/**
+ * When loading extensions through the extension registration system, this
+ * can be used to invalidate the cache. A good idea would be to set this to
+ * one file, you can just `touch` that one to invalidate the cache
+ *
+ * @par Example:
+ * @code
+ * $wgExtensionInfoMtime = filemtime( "$IP/LocalSettings.php" );
+ * @endcode
+ *
+ * If set to false, the mtime for each individual JSON file will be checked,
+ * which can be slow if a large number of extensions are being loaded.
+ *
+ * @var int|bool
+ */
+$wgExtensionInfoMTime = false;
+
/** @} */ # end of cache settings
/************************************************************************//**
@@ -2686,8 +2761,8 @@ $wgBrowserBlackList = array(
$wgLegacySchemaConversion = false;
/**
- * Enable dates like 'May 12' instead of '12 May', this only takes effect if
- * the interface is set to English.
+ * Enable dates like 'May 12' instead of '12 May', if the default date format
+ * is 'dmy or mdy'.
*/
$wgAmericanDates = false;
@@ -3050,6 +3125,7 @@ $wgEditPageFrameOptions = 'DENY';
* - 'DENY': Do not allow framing. This is recommended for most wikis.
* - 'SAMEORIGIN': Allow framing by pages on the same domain.
* - false: Allow all framing.
+ * Note: $wgBreakFrames will override this for human formatted API output.
*/
$wgApiFrameOptions = 'DENY';
@@ -3088,6 +3164,7 @@ $wgExperimentalHtmlIds = false;
* for the icon, the following keys are used:
* - src: An absolute url to the image to use for the icon, this is recommended
* but not required, however some skins will ignore icons without an image
+ * - srcset: optional additional-resolution images; see HTML5 specs
* - url: The url to use in the a element around the text or icon, if not set an a element will
* not be outputted
* - alt: This is the text form of the icon, it will be displayed without an image in
@@ -3104,7 +3181,9 @@ $wgFooterIcons = array(
),
"poweredby" => array(
"mediawiki" => array(
- // src defaults to "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
+ // Defaults to point at
+ // "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
+ // plus srcset for 1.5x, 2x resolution variants.
"src" => null,
"url" => "//www.mediawiki.org/",
"alt" => "Powered by MediaWiki",
@@ -3161,6 +3240,8 @@ $wgEnableCanonicalServerLink = false;
* <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.
+ *
+ * @since 1.25
*/
$wgMangleFlashPolicy = true;
@@ -3239,8 +3320,8 @@ $wgResourceModules = array();
* ),
* );
* // Note the '+' character:
- * $wgResourceModuleSkinStyles['+foo'] = array(
- * 'bar' => 'skins/Foo/bar.css',
+ * $wgResourceModuleSkinStyles['foo'] = array(
+ * '+bar' => 'skins/Foo/bar.css',
* );
* @endcode
*
@@ -3267,8 +3348,6 @@ $wgResourceModules = array();
*
* 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
@@ -3278,19 +3357,6 @@ $wgResourceModules = array();
* '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();
@@ -3374,15 +3440,6 @@ $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.
*
@@ -3480,6 +3537,9 @@ $wgResourceLoaderExperimentalAsyncLoading = false;
*
* Changes to LESS variables do not trigger cache invalidation.
*
+ * If the LESS variables need to be dynamic, you can use the
+ * ResourceLoaderGetLessVars hook (since 1.25).
+ *
* @par Example:
* @code
* $wgResourceLoaderLESSVars = array(
@@ -3715,6 +3775,18 @@ $wgInterwikiFallbackSite = 'wiki';
/** @} */ # end of Interwiki caching settings.
/**
+ * @name SiteStore caching settings.
+ * @{
+ */
+
+/**
+ * Specify the file location for the Sites json cache file.
+ */
+$wgSitesCacheFile = false;
+
+/** @} */ # end of SiteStore caching settings.
+
+/**
* If local interwikis are set up which allow redirects,
* set this regexp to restrict URLs which will be displayed
* as 'redirected from' links.
@@ -3785,19 +3857,12 @@ $wgNamespacesWithSubpages = array(
* A message with the suffix '-desc' should be added as a description message
* to have extra information on Special:TrackingCategories.
*
+ * @deprecated since 1.25 Extensions should now register tracking categories using
+ * the new extension registration system.
+ *
* @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',
-);
+$wgTrackingCategories = array();
/**
* Array of namespaces which can be deemed to contain valid "content", as far
@@ -3913,7 +3978,7 @@ $wgUrlProtocols = array(
);
/**
- * If true, removes (substitutes) templates in "~~~~" signatures.
+ * If true, removes (by substituting) templates in signatures.
*/
$wgCleanSignatures = true;
@@ -4109,15 +4174,6 @@ $wgTranscludeCacheExpiry = 3600;
$wgArticleCountMethod = 'link';
/**
- * wgHitcounterUpdateFreq sets how often page counters should be updated, higher
- * values are easier on the database. A value of 1 causes the counters to be
- * updated on every hit, any higher value n cause them to update *on average*
- * every n hits. Should be set to either 1 or something largish, eg 1000, for
- * maximum efficiency.
- */
-$wgHitcounterUpdateFreq = 1;
-
-/**
* How many days user must be idle before he is considered inactive. Will affect
* the number shown on Special:Statistics, Special:ActiveUsers, and the
* {{NUMBEROFACTIVEUSERS}} magic word in wikitext.
@@ -4279,7 +4335,7 @@ $wgDefaultUserOptions = array(
'enotifrevealaddr' => 0,
'enotifusertalkpages' => 1,
'enotifwatchlistpages' => 1,
- 'extendwatchlist' => 0,
+ 'extendwatchlist' => 1,
'fancysig' => 0,
'forceeditsummary' => 0,
'gender' => 'unknown',
@@ -4305,7 +4361,7 @@ $wgDefaultUserOptions = array(
'thumbsize' => 5,
'underline' => 2,
'uselivepreview' => 0,
- 'usenewrc' => 0,
+ 'usenewrc' => 1,
'watchcreations' => 1,
'watchdefault' => 1,
'watchdeletion' => 0,
@@ -4517,6 +4573,8 @@ $wgGroupPermissions['user']['reupload-shared'] = true;
$wgGroupPermissions['user']['minoredit'] = true;
$wgGroupPermissions['user']['purge'] = true; // can use ?action=purge without clicking "ok"
$wgGroupPermissions['user']['sendemail'] = true;
+$wgGroupPermissions['user']['applychangetags'] = true;
+$wgGroupPermissions['user']['changetags'] = true;
// Implicit group for accounts that pass $wgAutoConfirmAge
$wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true;
@@ -4577,6 +4635,7 @@ $wgGroupPermissions['sysop']['suppressredirect'] = true;
#$wgGroupPermissions['sysop']['pagelang'] = true;
#$wgGroupPermissions['sysop']['upload_by_url'] = true;
$wgGroupPermissions['sysop']['mergehistory'] = true;
+$wgGroupPermissions['sysop']['managechangetags'] = true;
// Permission to change users' group assignments
$wgGroupPermissions['bureaucrat']['userrights'] = true;
@@ -4793,7 +4852,6 @@ $wgAutopromote = array(
* @endcode
* Where event is either:
* - 'onEdit' (when user edits)
- * - 'onView' (when user views the wiki)
*
* Criteria has the same format as $wgAutopromote
*
@@ -4802,7 +4860,6 @@ $wgAutopromote = array(
*/
$wgAutopromoteOnce = array(
'onEdit' => array(),
- 'onView' => array()
);
/**
@@ -4992,6 +5049,17 @@ $wgRateLimits = array(
'ip' => null,
'subnet' => null,
),
+ 'stashedit' => array( // stashing edits into cache before save
+ 'anon' => null,
+ 'user' => null,
+ 'newbie' => null,
+ 'ip' => null,
+ 'subnet' => null,
+ ),
+ 'changetag' => array( // adding or removing change tags
+ 'user' => null,
+ 'newbie' => null,
+ ),
);
/**
@@ -5209,9 +5277,11 @@ $wgDebugDumpSqlLength = 500;
* 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.
+ * - associative array with keys:
+ * - 'destination' desired filename or URI.
+ * - 'sample' an integer value, specifying a sampling factor (optional)
+ * - 'level' A \Psr\Log\LogLevel constant, indicating the minimum level
+ * to log (optional, since 1.25)
*
* @par Example:
* @code
@@ -5220,15 +5290,41 @@ $wgDebugDumpSqlLength = 500;
*
* @par Advanced example:
* @code
- * $wgDebugLogGroups['memcached'] = (
+ * $wgDebugLogGroups['memcached'] = array(
* 'destination' => '/var/log/mediawiki/memcached.log',
* 'sample' => 1000, // log 1 message out of every 1,000.
+ * 'level' => \Psr\Log\LogLevel::WARNING
* );
* @endcode
*/
$wgDebugLogGroups = array();
/**
+ * Default service provider for creating Psr\Log\LoggerInterface instances.
+ *
+ * The value should be an array suitable for use with
+ * ObjectFactory::getObjectFromSpec(). The created object is expected to
+ * implement the MediaWiki\Logger\Spi interface. See ObjectFactory for additional
+ * details.
+ *
+ * Alternately the MediaWiki\Logger\LoggerFactory::registerProvider method can
+ * be called to inject an MediaWiki\Logger\Spi instance into the LoggerFactory
+ * and bypass the use of this configuration variable entirely.
+ *
+ * @par To completely disable logging:
+ * @code
+ * $wgMWLoggerDefaultSpi = array( 'class' => '\\MediaWiki\\Logger\\NullSpi' );
+ * @endcode
+ *
+ * @since 1.25
+ * @var array $wgMWLoggerDefaultSpi
+ * @see MwLogger
+ */
+$wgMWLoggerDefaultSpi = array(
+ 'class' => '\\MediaWiki\\Logger\\LegacySpi',
+);
+
+/**
* Display debug data at the bottom of the main content area.
*
* Useful for developers and technical users trying to working on a closed wiki.
@@ -5308,6 +5404,7 @@ $wgDeprecationReleaseLimit = false;
/**
* Only record profiling info for pages that took longer than this
+ * @deprecated since 1.25: set $wgProfiler['threshold'] instead.
*/
$wgProfileLimit = 0.0;
@@ -5326,8 +5423,10 @@ $wgProfileCallTree = false;
/**
* Should application server host be put into profiling table
+ *
+ * @deprecated set $wgProfiler['perhost'] = true instead
*/
-$wgProfilePerHost = false;
+$wgProfilePerHost = null;
/**
* Host for UDP profiler.
@@ -5335,14 +5434,18 @@ $wgProfilePerHost = false;
* The host should be running a daemon which can be obtained from MediaWiki
* Git at:
* http://git.wikimedia.org/tree/operations%2Fsoftware.git/master/udpprofile
+ *
+ * @deprecated set $wgProfiler['udphost'] instead
*/
-$wgUDPProfilerHost = '127.0.0.1';
+$wgUDPProfilerHost = null;
/**
* Port for UDP profiler.
* @see $wgUDPProfilerHost
+ *
+ * @deprecated set $wgProfiler['udpport'] instead
*/
-$wgUDPProfilerPort = '3811';
+$wgUDPProfilerPort = null;
/**
* Format string for the UDP profiler. The UDP profiler invokes sprintf() with
@@ -5352,13 +5455,10 @@ $wgUDPProfilerPort = '3811';
*
* @see $wgStatsFormatString
* @since 1.22
+ *
+ * @deprecated set $wgProfiler['udpformat'] instead
*/
-$wgUDPProfilerFormatString = "%s - %d %f %f %f %f %s\n";
-
-/**
- * Output debug message on every wfProfileIn/wfProfileOut
- */
-$wgDebugFunctionEntry = false;
+$wgUDPProfilerFormatString = null;
/**
* Destination for wfIncrStats() data...
@@ -5390,12 +5490,6 @@ $wgAggregateStatsID = false;
$wgStatsFormatString = "stats/%s - %s 1 1 1 1 %s\n";
/**
- * Whereas to count the number of time an article is viewed.
- * Does not work if pages are cached (for example with squid).
- */
-$wgDisableCounters = false;
-
-/**
* InfoAction retrieves a list of transclusion links (both to and from).
* This number puts a limit on that query in the case of highly transcluded
* templates.
@@ -5430,25 +5524,6 @@ $wgParserTestFiles = array(
$wgEnableJavaScriptTest = false;
/**
- * Configuration for javascript testing.
- */
-$wgJavaScriptTestConfig = array(
- 'qunit' => array(
- // Page where documentation can be found relevant to the QUnit test suite being ran.
- // Used in the intro paragraph on [[Special:JavaScriptTest/qunit]] for the
- // documentation link in the "javascripttest-qunit-intro" message.
- 'documentation' => '//www.mediawiki.org/wiki/Manual:JavaScript_unit_testing',
- // If you are submitting the QUnit test suite to a TestSwarm instance,
- // point this to the "inject.js" script of that instance. This is was registers
- // the QUnit hooks to extract the test results and push them back up into the
- // TestSwarm database.
- // @example 'http://localhost/testswarm/js/inject.js'
- // @example '//integration.mediawiki.org/testswarm/js/inject.js'
- 'testswarm-injectjs' => false,
- ),
-);
-
-/**
* Overwrite the caching key prefix with custom value.
* @since 1.19
*/
@@ -5494,10 +5569,25 @@ $wgSearchHighlightBoundaries = '[\p{Z}\p{P}\p{C}]';
* PHP wrapper to avoid firing up mediawiki for every keystroke
*
* Placeholders: {searchTerms}
+ *
+ * @deprecated since 1.25 Use $wgOpenSearchTemplates['application/x-suggestions+json'] instead
*/
$wgOpenSearchTemplate = false;
/**
+ * Templates for OpenSearch suggestions, defaults to API action=opensearch
+ *
+ * Sites with heavy load would typically have these point to a custom
+ * PHP wrapper to avoid firing up mediawiki for every keystroke
+ *
+ * Placeholders: {searchTerms}
+ */
+$wgOpenSearchTemplates = array(
+ 'application/x-suggestions+json' => false,
+ 'application/x-suggestions+xml' => false,
+);
+
+/**
* Enable OpenSearch suggestions requested by MediaWiki. Set this to
* false if you've disabled scripts that use api?action=opensearch and
* want reduce load caused by cached scripts still pulling suggestions.
@@ -5512,6 +5602,11 @@ $wgEnableOpenSearchSuggest = true;
$wgOpenSearchDefaultLimit = 10;
/**
+ * Minimum length of extract in <Description>. Actual extracts will last until the end of sentence.
+ */
+$wgOpenSearchDescriptionLength = 100;
+
+/**
* Expiry time for search suggestion responses
*/
$wgSearchSuggestCacheExpiry = 1200;
@@ -5722,9 +5817,9 @@ $wgGitRepositoryViewers = array(
/**
* Recentchanges items are periodically purged; entries older than this many
* seconds will go.
- * Default: 13 weeks = about three months
+ * Default: 90 days = about three months
*/
-$wgRCMaxAge = 13 * 7 * 24 * 3600;
+$wgRCMaxAge = 90 * 24 * 3600;
/**
* Filter $wgRCLinkDays by $wgRCMaxAge to avoid showing links for numbers
@@ -5877,11 +5972,6 @@ $wgAdvertisedFeedTypes = array( 'atom' );
$wgRCShowWatchingUsers = false; # UPO
/**
- * Show watching users in Page views
- */
-$wgPageShowWatchingUsers = false;
-
-/**
* Show the amount of changed characters in recent changes
*/
$wgRCShowChangedSize = true;
@@ -6160,6 +6250,8 @@ $wgExtensionMessagesFiles = array();
* en.json, de.json, etc. Extensions with messages in multiple places may specify an array of
* message directories.
*
+ * Message directories in core should be added to LocalisationCache::getMessagesDirs()
+ *
* @par Simple example:
* @code
* $wgMessagesDirs['Example'] = __DIR__ . '/i18n';
@@ -6175,10 +6267,7 @@ $wgExtensionMessagesFiles = array();
* @endcode
* @since 1.23
*/
-$wgMessagesDirs = array(
- 'core' => "$IP/languages/i18n",
- 'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
-);
+$wgMessagesDirs = array();
/**
* Array of files with list(s) of extension entry points to be used in
@@ -6254,7 +6343,7 @@ $wgAutoloadAttemptLowercase = true;
* 'version' => '1.9.0',
* 'url' => 'http://example.org/example-extension/',
* 'descriptionmsg' => 'exampleextension-desc',
- * 'license-name' => 'GPL-2.0',
+ * 'license-name' => 'GPL-2.0+',
* );
* @endcode
*
@@ -6288,7 +6377,7 @@ $wgAutoloadAttemptLowercase = true;
* localizable message (omit in favour of 'descriptionmsg').
*
* - 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).
+ * as "GPL-2.0+" or "MIT" (https://spdx.org/licenses/ for a list of identifiers).
*/
$wgExtensionCredits = array();
@@ -6340,7 +6429,6 @@ $wgHooks = array();
*/
$wgJobClasses = array(
'refreshLinks' => 'RefreshLinksJob',
- 'refreshLinks2' => 'RefreshLinksJob2', // b/c
'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
'sendMail' => 'EmaillingJob',
'enotifNotify' => 'EnotifNotifyJob',
@@ -6348,6 +6436,10 @@ $wgJobClasses = array(
'uploadFromUrl' => 'UploadFromUrlJob',
'AssembleUploadChunks' => 'AssembleUploadChunksJob',
'PublishStashedFile' => 'PublishStashedFileJob',
+ 'ThumbnailRender' => 'ThumbnailRenderJob',
+ 'recentChangesUpdate' => 'RecentChangesUpdateJob',
+ 'refreshLinksPrioritized' => 'RefreshLinksJob', // for cascading protection
+ 'enqueue' => 'EnqueueJob', // local queue for multi-DC setups
'null' => 'NullJob'
);
@@ -6390,7 +6482,7 @@ $wgJobTypeConf = array(
* These settings should be global to all wikis.
*/
$wgJobQueueAggregator = array(
- 'class' => 'JobQueueAggregatorMemc'
+ 'class' => 'JobQueueAggregatorNull'
);
/**
@@ -6398,8 +6490,7 @@ $wgJobQueueAggregator = array(
* Expensive Querypages are already updated.
*/
$wgSpecialPageCacheUpdates = array(
- 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' ),
- 'Activeusers' => array( 'SpecialActiveUsers', 'cacheUpdate' ),
+ 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' )
);
/**
@@ -6497,6 +6588,8 @@ $wgLogTypes = array(
'patrol',
'merge',
'suppress',
+ 'tag',
+ 'managetags',
);
/**
@@ -6533,7 +6626,8 @@ $wgLogRestrictions = array(
* for the link text.
*/
$wgFilterLogTypes = array(
- 'patrol' => true
+ 'patrol' => true,
+ 'tag' => true,
);
/**
@@ -6589,22 +6683,14 @@ $wgLogHeaders = array(
* Extensions with custom log types may add to this array.
*/
$wgLogActions = array(
- 'block/block' => 'blocklogentry',
- 'block/unblock' => 'unblocklogentry',
- 'block/reblock' => 'reblock-logentry',
'protect/protect' => 'protectedarticle',
'protect/modify' => 'modifiedarticleprotection',
'protect/unprotect' => 'unprotectedarticle',
'protect/move_prot' => 'movedarticleprotection',
- 'import/upload' => 'import-logentry-upload',
- 'import/interwiki' => 'import-logentry-interwiki',
- 'merge/merge' => 'pagemerge-logentry',
- 'suppress/block' => 'blocklogentry',
- 'suppress/reblock' => 'reblock-logentry',
);
/**
- * The same as above, but here values are names of functions,
+ * The same as above, but here values are names of classes,
* not messages.
* @see LogPage::actionText
* @see LogFormatter
@@ -6622,9 +6708,22 @@ $wgLogActionsHandlers = array(
'patrol/patrol' => 'PatrolLogFormatter',
'rights/rights' => 'RightsLogFormatter',
'rights/autopromote' => 'RightsLogFormatter',
- 'upload/upload' => 'LogFormatter',
- 'upload/overwrite' => 'LogFormatter',
- 'upload/revert' => 'LogFormatter',
+ 'upload/upload' => 'UploadLogFormatter',
+ 'upload/overwrite' => 'UploadLogFormatter',
+ 'upload/revert' => 'UploadLogFormatter',
+ 'merge/merge' => 'MergeLogFormatter',
+ 'tag/update' => 'TagLogFormatter',
+ 'managetags/create' => 'LogFormatter',
+ 'managetags/delete' => 'LogFormatter',
+ 'managetags/activate' => 'LogFormatter',
+ 'managetags/deactivate' => 'LogFormatter',
+ 'block/block' => 'BlockLogFormatter',
+ 'block/unblock' => 'BlockLogFormatter',
+ 'block/reblock' => 'BlockLogFormatter',
+ 'suppress/block' => 'BlockLogFormatter',
+ 'suppress/reblock' => 'BlockLogFormatter',
+ 'import/upload' => 'LogFormatter',
+ 'import/interwiki' => 'LogFormatter',
);
/**
@@ -6691,6 +6790,7 @@ $wgActions = array(
'credits' => true,
'delete' => true,
'edit' => true,
+ 'editchangetags' => 'SpecialPageAction',
'history' => true,
'info' => true,
'markpatrolled' => true,
@@ -6699,7 +6799,7 @@ $wgActions = array(
'raw' => true,
'render' => true,
'revert' => true,
- 'revisiondelete' => true,
+ 'revisiondelete' => 'SpecialPageAction',
'rollback' => true,
'submit' => true,
'unprotect' => true,
@@ -6967,6 +7067,12 @@ $wgAjaxUploadDestCheck = true;
$wgAjaxLicensePreview = true;
/**
+ * Have clients send edits to be prepared when filling in edit summaries.
+ * This gives the server a head start on the expensive parsing operation.
+ */
+$wgAjaxEditStash = true;
+
+/**
* Settings for incoming cross-site AJAX requests:
* Newer browsers support cross-site AJAX when the target resource allows requests
* from the origin domain by the Access-Control-Allow-Origin header.
@@ -7084,6 +7190,18 @@ $wgAsyncHTTPTimeout = 25;
$wgHTTPProxy = false;
/**
+ * Local virtual hosts.
+ *
+ * This lists domains that are configured as virtual hosts on the same machine.
+ * If a request is to be made to a domain listed here, or any subdomain thereof,
+ * then no proxy will be used.
+ * Command-line scripts are not affected by this setting and will always use
+ * proxy if it is configured.
+ * @since 1.25
+ */
+$wgLocalVirtualHosts = array();
+
+/**
* Timeout for connections done internally (in seconds)
* Only works for curl
*/
@@ -7286,13 +7404,21 @@ $wgPagePropsHaveSortkey = true;
$wgHttpsPort = 443;
/**
- * Secret and algorithm for hmac-based key derivation function (fast,
+ * Secret for hmac-based key derivation function (fast,
* cryptographically secure random numbers).
* This should be set in LocalSettings.php, otherwise wgSecretKey will
* be used.
+ * See also: $wgHKDFAlgorithm
* @since 1.24
*/
$wgHKDFSecret = false;
+
+/**
+ * Algorithm for hmac-based key derivation function (fast,
+ * cryptographically secure random numbers).
+ * See also: $wgHKDFSecret
+ * @since 1.24
+ */
$wgHKDFAlgorithm = 'sha256';
/**
@@ -7312,6 +7438,34 @@ $wgPageLanguageUseDB = false;
$wgUseLinkNamespaceDBFields = true;
/**
+ * Global configuration variable for Virtual REST Services.
+ * Parameters for different services are to be declared inside
+ * $wgVirtualRestConfig['modules'], which is to be treated as an associative
+ * array. Global parameters will be merged with service-specific ones. The
+ * result will then be passed to VirtualRESTService::__construct() in the
+ * module.
+ *
+ * Example config for Parsoid:
+ *
+ * $wgVirtualRestConfig['modules']['parsoid'] = array(
+ * 'url' => 'http://localhost:8000',
+ * 'prefix' => 'enwiki',
+ * );
+ *
+ * @var array
+ * @since 1.25
+ */
+$wgVirtualRestConfig = array(
+ 'modules' => array(),
+ 'global' => array(
+ # Timeout in seconds
+ 'timeout' => 360,
+ 'forwardCookies' => false,
+ 'HTTPProxy' => null
+ )
+);
+
+/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
* @}
diff --git a/includes/Defines.php b/includes/Defines.php
index 017e9ea4..c9263da9 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -2,10 +2,6 @@
/**
* A few constants that might be needed during LocalSettings.php.
*
- * Note: these constants must all be resolvable at compile time by HipHop,
- * since this file will not be executed during request startup for a compiled
- * 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
@@ -152,12 +148,13 @@ define( 'AV_SCAN_FAILED', false ); #scan failed (scanner not found or error in
/**@{
* Anti-lock flags
- * See DefaultSettings.php for a description
+ * Was used by $wgAntiLockFlags, which was removed with 1.25
+ * Constants kept to not have warnings when used in LocalSettings
*/
define( 'ALF_PRELOAD_LINKS', 1 ); // unused
define( 'ALF_PRELOAD_EXISTENCE', 2 ); // unused
-define( 'ALF_NO_LINK_LOCK', 4 );
-define( 'ALF_NO_BLOCK_LOCK', 8 );
+define( 'ALF_NO_LINK_LOCK', 4 ); // unused
+define( 'ALF_NO_BLOCK_LOCK', 8 ); // unused
/**@}*/
/**@{
@@ -206,15 +203,15 @@ define( 'LIST_OR', 4 );
/**
* Unicode and normalisation related
*/
-require_once __DIR__ . '/normal/UtfNormalDefines.php';
+require_once __DIR__ . '/libs/normal/UtfNormalDefines.php';
/**@{
* Hook support constants
*/
-define( 'MW_SUPPORTS_EDITFILTERMERGED', 1 );
define( 'MW_SUPPORTS_PARSERFIRSTCALLINIT', 1 );
define( 'MW_SUPPORTS_LOCALISATIONCACHE', 1 );
define( 'MW_SUPPORTS_CONTENTHANDLER', 1 );
+define( 'MW_EDITFILTERMERGED_SUPPORTS_API', 1 );
/**@}*/
/** Support for $wgResourceModules */
@@ -223,6 +220,12 @@ define( 'MW_SUPPORTS_RESOURCE_MODULES', 1 );
/**@{
* Allowed values for Parser::$mOutputType
* Parameter to Parser::startExternalParse().
+ * Use of Parser consts is preferred:
+ * - Parser::OT_HTML
+ * - Parser::OT_WIKI
+ * - Parser::OT_PREPROCESS
+ * - Parser::OT_MSG
+ * - Parser::OT_PLAIN
*/
define( 'OT_HTML', 1 );
define( 'OT_WIKI', 2 );
@@ -233,16 +236,14 @@ define( 'OT_PLAIN', 4 );
/**@{
* Flags for Parser::setFunctionHook
+ * Use of Parser consts is preferred:
+ * - Parser::SFH_NO_HASH
+ * - Parser::SFH_OBJECT_ARGS
*/
define( 'SFH_NO_HASH', 1 );
define( 'SFH_OBJECT_ARGS', 2 );
/**@}*/
-/**
- * Flags for Parser::replaceLinkHolders
- */
-define( 'RLH_FOR_UPDATE', 1 );
-
/**@{
* Autopromote conditions (must be here and not in Autopromote.php, so that
* they're loaded for DefaultSettings.php before AutoLoader.php)
diff --git a/includes/EditPage.php b/includes/EditPage.php
index 38c80ba8..8d27eac8 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -151,6 +151,18 @@ class EditPage {
const AS_NO_CHANGE_CONTENT_MODEL = 235;
/**
+ * Status: user tried to create self-redirect (redirect to the same article) and
+ * wpIgnoreSelfRedirect == false
+ */
+ const AS_SELF_REDIRECT = 236;
+
+ /**
+ * Status: an error relating to change tagging. Look at the message key for
+ * more details
+ */
+ const AS_CHANGE_TAG_ERROR = 237;
+
+ /**
* Status: can't parse content
*/
const AS_PARSE_ERROR = 240;
@@ -256,6 +268,12 @@ class EditPage {
/** @var bool */
protected $allowBlankArticle = false;
+ /** @var bool */
+ protected $selfRedirect = false;
+
+ /** @var bool */
+ protected $allowSelfRedirect = false;
+
/** @var string */
public $autoSumm = '';
@@ -321,6 +339,9 @@ class EditPage {
/** @var int */
public $oldid = 0;
+ /** @var int */
+ public $parentRevId = 0;
+
/** @var string */
public $editintro = '';
@@ -336,6 +357,9 @@ class EditPage {
/** @var null|string */
public $contentFormat = null;
+ /** @var null|array */
+ public $changeTags = null;
+
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
@@ -362,9 +386,6 @@ class EditPage {
/** @var bool */
protected $edit;
- /** @var bool */
- public $live;
-
/**
* @param Article $article
*/
@@ -448,29 +469,21 @@ class EditPage {
function edit() {
global $wgOut, $wgRequest, $wgUser;
// Allow extensions to modify/prevent this form or submission
- if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
+ if ( !Hooks::run( 'AlternateEdit', array( $this ) ) ) {
return;
}
- wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . ": enter\n" );
// If they used redlink=1 and the page exists, redirect to the main article
if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
$wgOut->redirect( $this->mTitle->getFullURL() );
- wfProfileOut( __METHOD__ );
return;
}
$this->importFormData( $wgRequest );
$this->firsttime = false;
- if ( $this->live ) {
- $this->livePreview();
- wfProfileOut( __METHOD__ );
- return;
- }
-
if ( wfReadOnly() && $this->save ) {
// Force preview
$this->save = false;
@@ -492,7 +505,7 @@ class EditPage {
}
}
- $permErrors = $this->getEditPermissionErrors();
+ $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' );
if ( $permErrors ) {
wfDebug( __METHOD__ . ": User can't edit\n" );
// Auto-block user's IP if the account was "hard" blocked
@@ -500,12 +513,9 @@ class EditPage {
$this->displayPermissionsError( $permErrors );
- wfProfileOut( __METHOD__ );
return;
}
- wfProfileIn( __METHOD__ . "-business-end" );
-
$this->isConflict = false;
// css / js subpages of user pages get a special treatment
$this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
@@ -525,9 +535,9 @@ class EditPage {
# in the back door with a hand-edited submission URL.
if ( 'save' == $this->formtype ) {
- if ( !$this->attemptSave() ) {
- wfProfileOut( __METHOD__ . "-business-end" );
- wfProfileOut( __METHOD__ );
+ $resultDetails = null;
+ $status = $this->attemptSave( $resultDetails );
+ if ( !$this->handleStatus( $status, $resultDetails ) ) {
return;
}
}
@@ -537,34 +547,37 @@ class EditPage {
if ( 'initial' == $this->formtype || $this->firsttime ) {
if ( $this->initialiseForm() === false ) {
$this->noSuchSectionPage();
- wfProfileOut( __METHOD__ . "-business-end" );
- wfProfileOut( __METHOD__ );
return;
}
if ( !$this->mTitle->getArticleID() ) {
- wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
+ Hooks::run( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
} else {
- wfRunHooks( 'EditFormInitialText', array( $this ) );
+ Hooks::run( 'EditFormInitialText', array( $this ) );
}
}
$this->showEditForm();
- wfProfileOut( __METHOD__ . "-business-end" );
- wfProfileOut( __METHOD__ );
}
/**
+ * @param string $rigor Same format as Title::getUserPermissionErrors()
* @return array
*/
- protected function getEditPermissionErrors() {
+ protected function getEditPermissionErrors( $rigor = 'secure' ) {
global $wgUser;
- $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
+
+ $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser, $rigor );
# Can this title be created?
if ( !$this->mTitle->exists() ) {
- $permErrors = array_merge( $permErrors,
- wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) );
+ $permErrors = array_merge(
+ $permErrors,
+ wfArrayDiff2(
+ $this->mTitle->getUserPermissionsErrors( 'create', $wgUser, $rigor ),
+ $permErrors
+ )
+ );
}
# Ignore some permissions errors when a user is just previewing/viewing diffs
$remove = array();
@@ -576,6 +589,7 @@ class EditPage {
}
}
$permErrors = wfArrayDiff2( $permErrors, $remove );
+
return $permErrors;
}
@@ -612,7 +626,7 @@ class EditPage {
throw new PermissionsError( $action, $permErrors );
}
- wfRunHooks( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) );
+ Hooks::run( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setPageTitle( wfMessage(
@@ -717,13 +731,10 @@ class EditPage {
function importFormData( &$request ) {
global $wgContLang, $wgUser;
- wfProfileIn( __METHOD__ );
-
# Section edit can come from either the form or a link
$this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
- wfProfileOut( __METHOD__ );
throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
}
@@ -738,13 +749,10 @@ class EditPage {
// Skip this if wpTextbox2 has input, it indicates that we came
// from a conflict page with raw page text, not a custom form
// modified by subclasses
- wfProfileIn( get_class( $this ) . "::importContentFormData" );
$textbox1 = $this->importContentFormData( $request );
if ( $textbox1 !== null ) {
$this->textbox1 = $textbox1;
}
-
- wfProfileOut( get_class( $this ) . "::importContentFormData" );
}
# Truncate for whole multibyte characters
@@ -785,7 +793,8 @@ class EditPage {
// 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 ) );
+ $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' )
+ && is_null( $this->edittime ) );
}
if ( $this->incompleteForm ) {
# If the form is incomplete, force to preview.
@@ -793,8 +802,7 @@ class EditPage {
wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
$this->preview = true;
} else {
- /* Fallback for live preview */
- $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
+ $this->preview = $request->getCheck( 'wpPreview' );
$this->diff = $request->getCheck( 'wpDiff' );
// Remember whether a save was requested, so we can indicate
@@ -844,6 +852,15 @@ class EditPage {
$this->autoSumm = $request->getText( 'wpAutoSummary' );
$this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
+ $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
+
+ $changeTags = $request->getVal( 'wpChangeTags' );
+ if ( is_null( $changeTags ) || $changeTags === '' ) {
+ $this->changeTags = array();
+ } else {
+ $this->changeTags = array_filter( array_map( 'trim', explode( ',',
+ $changeTags ) ) );
+ }
} else {
# Not a posted form? Start with nothing.
wfDebug( __METHOD__ . ": Not a posted form.\n" );
@@ -880,6 +897,7 @@ class EditPage {
}
$this->oldid = $request->getInt( 'oldid' );
+ $this->parentRevId = $request->getInt( 'parentRevId' );
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
@@ -905,15 +923,13 @@ class EditPage {
* allowed.
*/
- $this->live = $request->getCheck( 'live' );
$this->editintro = $request->getText( 'editintro',
// Custom edit intro for new sections
$this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
// Allow extensions to modify form data
- wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
+ Hooks::run( 'EditPage::importFormData', array( $this, $request ) );
- wfProfileOut( __METHOD__ );
}
/**
@@ -974,8 +990,6 @@ class EditPage {
protected function getContentObject( $def_content = null ) {
global $wgOut, $wgRequest, $wgUser, $wgContLang;
- wfProfileIn( __METHOD__ );
-
$content = false;
// For message page not locally set, use the i18n message.
@@ -1087,7 +1101,6 @@ class EditPage {
}
}
- wfProfileOut( __METHOD__ );
return $content;
}
@@ -1280,18 +1293,20 @@ class EditPage {
/**
* Attempt submission
+ * @param array $resultDetails See docs for $result in internalAttemptSave
* @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
- * @return bool False if output is done, true if the rest of the form should be displayed
+ * @return Status The resulting status object.
*/
- public function attemptSave() {
+ public function attemptSave( &$resultDetails = false ) {
global $wgUser;
- $resultDetails = false;
# Allow bots to exempt some edits from bot flagging
$bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
$status = $this->internalAttemptSave( $resultDetails, $bot );
- return $this->handleStatus( $status, $resultDetails );
+ Hooks::run( 'EditPage::attemptSave:after', array( $this, $status, $resultDetails ) );
+
+ return $status;
}
/**
@@ -1329,6 +1344,7 @@ class EditPage {
case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
case self::AS_END:
case self::AS_BLANK_ARTICLE:
+ case self::AS_SELF_REDIRECT:
return true;
case self::AS_HOOK_ERROR:
@@ -1349,7 +1365,7 @@ class EditPage {
$sectionanchor = $resultDetails['sectionanchor'];
// Give extensions a chance to modify URL query on update
- wfRunHooks(
+ Hooks::run(
'ArticleUpdateBeforeRedirect',
array( $this->mArticle, &$sectionanchor, &$extraQuery )
);
@@ -1415,8 +1431,8 @@ class EditPage {
protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
// Run old style post-section-merge edit filter
if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
- array( $this, $content, &$this->hookError, $this->summary ) ) ) {
-
+ array( $this, $content, &$this->hookError, $this->summary ) )
+ ) {
# Error messages etc. could be handled within the hook...
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR;
@@ -1429,15 +1445,24 @@ class EditPage {
}
// Run new style post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMergedContent',
- array( $this->mArticle->getContext(), $content, $status, $this->summary,
- $user, $this->minoredit ) ) ) {
-
+ if ( !Hooks::run( 'EditFilterMergedContent',
+ array( $this->mArticle->getContext(), $content, $status, $this->summary,
+ $user, $this->minoredit ) )
+ ) {
# Error messages etc. could be handled within the hook...
- // XXX: $status->value may already be something informative...
- $this->hookError = $status->getWikiText();
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR;
+ if ( $status->isGood() ) {
+ $status->fatal( 'hookaborted' );
+ // Not setting $this->hookError here is a hack to allow the hook
+ // to cause a return to the edit page without $this->hookError
+ // being set. This is used by ConfirmEdit to display a captcha
+ // without any error message cruft.
+ } else {
+ $this->hookError = $status->getWikiText();
+ }
+ // Use the existing $status->value if the hook set it
+ if ( !$status->value ) {
+ $status->value = self::AS_HOOK_ERROR;
+ }
return false;
} elseif ( !$status->isOK() ) {
# ...or the hook could be expecting us to produce an error
@@ -1510,15 +1535,10 @@ class EditPage {
$status = Status::newGood();
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-checks' );
-
- if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
+ if ( !Hooks::run( 'EditPage::attemptSave', array( $this ) ) ) {
wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1535,8 +1555,6 @@ class EditPage {
);
$status->fatal( 'spamprotectionmatch', false );
$status->value = self::AS_SPAM_ERROR;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1551,8 +1569,6 @@ class EditPage {
$ex->getMessage()
);
$status->value = self::AS_PARSE_ERROR;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1564,9 +1580,6 @@ class EditPage {
$code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
$status->setResult( false, $code );
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
-
return $status;
}
@@ -1595,26 +1608,20 @@ class EditPage {
wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
$status->fatal( 'spamprotectionmatch', $match );
$status->value = self::AS_SPAM_ERROR;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
- if ( !wfRunHooks(
+ if ( !Hooks::run(
'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;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
} elseif ( $this->hookError != '' ) {
# ...or the hook could be expecting us to produce an error
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR_EXPECTED;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1623,8 +1630,6 @@ class EditPage {
$wgUser->spreadAnyEditBlock();
# Check block state against master, thus 'false'.
$status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1633,22 +1638,16 @@ class EditPage {
// Error will be displayed by showEditForm()
$this->tooBig = true;
$status->setResult( false, self::AS_CONTENT_TOO_BIG );
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
if ( !$wgUser->isAllowed( 'edit' ) ) {
if ( $wgUser->isAnon() ) {
$status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
} else {
$status->fatal( 'readonlytext' );
$status->value = self::AS_READ_ONLY_PAGE_LOGGED;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
}
@@ -1657,23 +1656,26 @@ class EditPage {
&& !$wgUser->isAllowed( 'editcontentmodel' )
) {
$status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
+ if ( $this->changeTags ) {
+ $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
+ $this->changeTags, $wgUser );
+ if ( !$changeTagsStatus->isOK() ) {
+ $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
+ return $changeTagsStatus;
+ }
+ }
+
if ( wfReadOnly() ) {
$status->fatal( 'readonlytext' );
$status->value = self::AS_READ_ONLY_PAGE;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
$status->fatal( 'actionthrottledtext' );
$status->value = self::AS_RATE_LIMITED;
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1681,13 +1683,9 @@ class EditPage {
# confirmation
if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
$status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
- wfProfileOut( __METHOD__ . '-checks' );
- wfProfileOut( __METHOD__ );
return $status;
}
- wfProfileOut( __METHOD__ . '-checks' );
-
# Load the page data from the master. If anything changes in the meantime,
# we detect it by using page_latest like a token in a 1 try compare-and-swap.
$this->mArticle->loadPageData( 'fromdbmaster' );
@@ -1699,7 +1697,6 @@ class EditPage {
$status->fatal( 'nocreatetext' );
$status->value = self::AS_NO_CREATE_PERMISSION;
wfDebug( __METHOD__ . ": no create permission\n" );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1717,12 +1714,10 @@ class EditPage {
$this->blankArticle = true;
$status->fatal( 'blankarticle' );
$status->setResult( false, self::AS_BLANK_ARTICLE );
- wfProfileOut( __METHOD__ );
return $status;
}
if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1827,12 +1822,10 @@ class EditPage {
if ( $this->isConflict ) {
$status->setResult( false, self::AS_CONFLICT_DETECTED );
- wfProfileOut( __METHOD__ );
return $status;
}
if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1842,7 +1835,6 @@ class EditPage {
$this->missingSummary = true;
$status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
$status->value = self::AS_SUMMARY_NEEDED;
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1851,7 +1843,6 @@ class EditPage {
$this->missingComment = true;
$status->fatal( 'missingcommenttext' );
$status->value = self::AS_TEXTBOX_EMPTY;
- wfProfileOut( __METHOD__ );
return $status;
}
} elseif ( !$this->allowBlankSummary
@@ -1862,12 +1853,10 @@ class EditPage {
$this->missingSummary = true;
$status->fatal( 'missingsummary' );
$status->value = self::AS_SUMMARY_NEEDED;
- wfProfileOut( __METHOD__ );
return $status;
}
# All's well
- wfProfileIn( __METHOD__ . '-sectionanchor' );
$sectionanchor = '';
if ( $this->section == 'new' ) {
$this->summary = $this->newSectionSummary( $sectionanchor );
@@ -1884,7 +1873,6 @@ class EditPage {
}
}
$result['sectionanchor'] = $sectionanchor;
- wfProfileOut( __METHOD__ . '-sectionanchor' );
// Save errors may fall down to the edit form, but we've now
// merged the section into full text. Clear the section field
@@ -1896,12 +1884,25 @@ class EditPage {
$status->value = self::AS_SUCCESS_UPDATE;
}
+ if ( !$this->allowSelfRedirect
+ && $content->isRedirect()
+ && $content->getRedirectTarget()->equals( $this->getTitle() )
+ ) {
+ // If the page already redirects to itself, don't warn.
+ $currentTarget = $this->getCurrentContent()->getRedirectTarget();
+ if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
+ $this->selfRedirect = true;
+ $status->fatal( 'selfredirect' );
+ $status->value = self::AS_SELF_REDIRECT;
+ return $status;
+ }
+ }
+
// Check for length errors again now that the section is merged in
$this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
$this->tooBig = true;
$status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1931,7 +1932,6 @@ class EditPage {
// Destroys data doEdit() put in $status->value but who cares
$doEditStatus->value = self::AS_END;
}
- wfProfileOut( __METHOD__ );
return $doEditStatus;
}
@@ -1941,8 +1941,18 @@ class EditPage {
$wgUser->pingLimiter( 'linkpurge' );
}
$result['redirect'] = $content->isRedirect();
+
$this->updateWatchlist();
- wfProfileOut( __METHOD__ );
+
+ if ( $this->changeTags && isset( $doEditStatus->value['revision'] ) ) {
+ // If a revision was created, apply any change tags that were requested
+ ChangeTags::addTags(
+ $this->changeTags,
+ isset( $doEditStatus->value['rc'] ) ? $doEditStatus->value['rc']->mAttribs['rc_id'] : null,
+ $doEditStatus->value['revision']->getId()
+ );
+ }
+
return $status;
}
@@ -1979,7 +1989,6 @@ class EditPage {
* @return bool
*/
private function mergeChangesIntoContent( &$editContent ) {
- wfProfileIn( __METHOD__ );
$db = wfGetDB( DB_MASTER );
@@ -1988,7 +1997,6 @@ class EditPage {
$baseContent = $baseRevision ? $baseRevision->getContent() : null;
if ( is_null( $baseContent ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -1997,7 +2005,6 @@ class EditPage {
$currentContent = $currentRevision ? $currentRevision->getContent() : null;
if ( is_null( $currentContent ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -2007,11 +2014,9 @@ class EditPage {
if ( $result ) {
$editContent = $result;
- wfProfileOut( __METHOD__ );
return true;
}
- wfProfileOut( __METHOD__ );
return false;
}
@@ -2070,19 +2075,31 @@ class EditPage {
}
function setHeaders() {
- global $wgOut, $wgUser;
+ global $wgOut, $wgUser, $wgAjaxEditStash;
$wgOut->addModules( 'mediawiki.action.edit' );
$wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
- if ( $wgUser->getOption( 'uselivepreview', false ) ) {
+ if ( $wgUser->getOption( 'showtoolbar' ) ) {
+ // The addition of default buttons is handled by getEditToolbar() which
+ // has its own dependency on this module. The call here ensures the module
+ // is loaded in time (it has position "top") for other modules to register
+ // buttons (e.g. extensions, gadgets, user scripts).
+ $wgOut->addModules( 'mediawiki.toolbar' );
+ }
+
+ if ( $wgUser->getOption( 'uselivepreview' ) ) {
$wgOut->addModules( 'mediawiki.action.edit.preview' );
}
- if ( $wgUser->getOption( 'useeditwarning', false ) ) {
+ if ( $wgUser->getOption( 'useeditwarning' ) ) {
$wgOut->addModules( 'mediawiki.action.edit.editWarning' );
}
+ if ( $wgAjaxEditStash ) {
+ $wgOut->addModules( 'mediawiki.action.edit.stash' );
+ }
+
$wgOut->setRobotPolicy( 'noindex,nofollow' );
# Enabled article-related sidebar, toplinks, etc.
@@ -2108,6 +2125,9 @@ class EditPage {
$displayTitle = $contextTitle->getPrefixedText();
}
$wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
+ # Transmit the name of the message to JavaScript for live preview
+ # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
+ $wgOut->addJsConfigVars( 'wgEditMessage', $msg );
}
/**
@@ -2124,6 +2144,17 @@ class EditPage {
if ( $namespace == NS_MEDIAWIKI ) {
# Show a warning if editing an interface message
$wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
+ # If this is a default message (but not css or js),
+ # show a hint that it is translatable on translatewiki.net
+ if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
+ && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
+ ) {
+ $defaultMessageText = $this->mTitle->getDefaultMessageText();
+ if ( $defaultMessageText !== false ) {
+ $wgOut->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
+ 'translateinterface' );
+ }
+ }
} elseif ( $namespace == NS_FILE ) {
# Show a hint to shared repo
$file = wfFindFile( $this->mTitle );
@@ -2155,7 +2186,8 @@ class EditPage {
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 ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # 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',
@@ -2222,7 +2254,10 @@ 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( '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>', $this->mTitle );
+ $wgOut->addWikiTextTitleTidy(
+ '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
+ $this->mTitle
+ );
return true;
}
}
@@ -2248,11 +2283,7 @@ class EditPage {
* $this->allowNonTextContent is not true.
*/
protected function toEditText( $content ) {
- if ( $content === null || $content === false ) {
- return $content;
- }
-
- if ( is_string( $content ) ) {
+ if ( $content === null || $content === false || is_string( $content ) ) {
return $content;
}
@@ -2300,12 +2331,13 @@ class EditPage {
* Send the edit form and related headers to $wgOut
* @param callable|null $formCallback That takes an OutputPage parameter; will be called
* during form output near the top, for captchas and the like.
+ *
+ * The $formCallback parameter is deprecated since MediaWiki 1.25. Please
+ * use the EditPage::showEditForm:fields hook instead.
*/
function showEditForm( $formCallback = null ) {
global $wgOut, $wgUser;
- wfProfileIn( __METHOD__ );
-
# need to parse the preview early so that we know which templates are used,
# otherwise users with "show preview after edit box" will get a blank list
# we parse this near the beginning so that setHeaders can do the title
@@ -2315,12 +2347,11 @@ class EditPage {
$previewOutput = $this->getPreviewText();
}
- wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
+ Hooks::run( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
$this->setHeaders();
if ( $this->showHeader() === false ) {
- wfProfileOut( __METHOD__ );
return;
}
@@ -2358,6 +2389,7 @@ class EditPage {
) );
if ( is_callable( $formCallback ) ) {
+ wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
call_user_func_array( $formCallback, array( &$wgOut ) );
}
@@ -2381,7 +2413,7 @@ class EditPage {
. Xml::closeElement( 'div' )
);
- wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
+ Hooks::run( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
// Put these up at the top to ensure they aren't lost on early form submission
$this->showFormBeforeText();
@@ -2425,6 +2457,10 @@ class EditPage {
$wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
}
+ if ( $this->selfRedirect ) {
+ $wgOut->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
+ }
+
if ( $this->hasPresetSummary ) {
// If a summary has been preset using &summary= we don't want to prompt for
// a different summary. Only prompt for a summary if the summary is blanked.
@@ -2436,6 +2472,8 @@ class EditPage {
$wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
$wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+ $wgOut->addHTML( Html::hidden( 'parentRevId',
+ $this->parentRevId ?: $this->mArticle->getRevIdFetched() ) );
$wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
$wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
@@ -2517,7 +2555,6 @@ class EditPage {
$this->displayPreviewArea( $previewOutput, false );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -2548,7 +2585,19 @@ class EditPage {
}
// Add edit notices
- $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
+ $editNotices = $this->mTitle->getEditNotices( $this->oldid );
+ if ( count( $editNotices ) ) {
+ $wgOut->addHTML( implode( "\n", $editNotices ) );
+ } else {
+ $msg = wfMessage( 'editnotice-notext' );
+ if ( !$msg->isDisabled() ) {
+ $wgOut->addHTML(
+ '<div class="mw-editnotice-notext">'
+ . $msg->parseAsBlock()
+ . '</div>'
+ );
+ }
+ }
if ( $this->isConflict ) {
$wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
@@ -2587,6 +2636,10 @@ class EditPage {
$wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' );
}
+ if ( $this->selfRedirect ) {
+ $wgOut->wrapWikiMsg( "<div id='mw-selfredirect'>\n$1\n</div>", 'selfredirect' );
+ }
+
if ( $this->hookError !== '' ) {
$wgOut->addWikiText( $this->hookError );
}
@@ -2846,7 +2899,7 @@ class EditPage {
global $wgOut;
$section = htmlspecialchars( $this->section );
$wgOut->addHTML( <<<HTML
-<input type='hidden' value="{$section}" name="wpSection" />
+<input type='hidden' value="{$section}" name="wpSection"/>
<input type='hidden' value="{$this->starttime}" name="wpStarttime" />
<input type='hidden' value="{$this->edittime}" name="wpEdittime" />
<input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
@@ -2964,7 +3017,7 @@ HTML
);
$pageLang = $this->mTitle->getPageLanguage();
- $attribs['lang'] = $pageLang->getCode();
+ $attribs['lang'] = $pageLang->getHtmlCode();
$attribs['dir'] = $pageLang->getDir();
$wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
@@ -2987,6 +3040,12 @@ HTML
if ( $this->formtype == 'preview' ) {
$this->showPreview( $previewOutput );
+ } else {
+ // Empty content container for LivePreview
+ $pageViewLang = $this->mTitle->getPageViewLanguage();
+ $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
+ 'class' => 'mw-content-' . $pageViewLang->getDir() );
+ $wgOut->addHTML( Html::rawElement( 'div', $attribs ) );
}
$wgOut->addHTML( '</div>' );
@@ -3019,7 +3078,7 @@ HTML
}
# This hook seems slightly odd here, but makes things more
# consistent for extensions.
- wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
+ Hooks::run( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
$wgOut->addHTML( $text );
if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
$this->mArticle->closeShowCategory();
@@ -3058,7 +3117,7 @@ HTML
if ( $newContent ) {
ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
- wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
+ Hooks::run( 'EditPageGetDiffContent', array( $this, &$newContent ) );
$popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
$newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
@@ -3110,7 +3169,7 @@ HTML
*/
protected function showTosSummary() {
$msg = 'editpage-tos-summary';
- wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
+ Hooks::run( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
if ( !wfMessage( $msg )->isDisabled() ) {
global $wgOut;
$wgOut->addHTML( '<div class="mw-tos-summary">' );
@@ -3154,7 +3213,7 @@ HTML
'[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
}
// Allow for site and per-namespace customization of contribution/copyright notice.
- wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
+ Hooks::run( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
return "<div id=\"editpage-copywarn\">\n" .
call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
@@ -3172,8 +3231,6 @@ HTML
return '';
}
- wfProfileIn( __METHOD__ );
-
$limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
wfMessage( 'limitreport-title' )->parseAsBlock()
);
@@ -3187,7 +3244,7 @@ HTML
Html::openElement( 'tbody' );
foreach ( $output->getLimitReportData() as $key => $value ) {
- if ( wfRunHooks( 'ParserLimitReportFormat',
+ if ( Hooks::run( 'ParserLimitReportFormat',
array( $key, &$value, &$limitReport, true, true )
) ) {
$keyMsg = wfMessage( $key );
@@ -3208,13 +3265,11 @@ HTML
Html::closeElement( 'table' ) .
Html::closeElement( 'div' );
- wfProfileOut( __METHOD__ );
-
return $limitReport;
}
protected function showStandardInputs( &$tabindex = 2 ) {
- global $wgOut, $wgUseMediaWikiUIEverywhere;
+ global $wgOut;
$wgOut->addHTML( "<div class='editOptions'>\n" );
if ( $this->section != 'new' ) {
@@ -3246,10 +3301,8 @@ HTML
'target' => 'helpwindow',
'href' => $edithelpurl,
);
- if ( $wgUseMediaWikiUIEverywhere ) {
- $attrs['class'] = 'mw-ui-button mw-ui-quiet';
- }
- $edithelp = Html::element( 'a', $attrs, wfMessage( 'edithelp' )->text() ) .
+ $edithelp = Html::linkButton( wfMessage( 'edithelp' )->text(),
+ $attrs, array( 'mw-ui-quiet' ) ) .
wfMessage( 'word-separator' )->escaped() .
wfMessage( 'newwindow' )->parse();
@@ -3257,7 +3310,7 @@ HTML
$wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
$wgOut->addHTML( "</div><!-- editButtons -->\n" );
- wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
+ Hooks::run( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
$wgOut->addHTML( "</div><!-- editOptions -->\n" );
}
@@ -3269,7 +3322,7 @@ HTML
protected function showConflict() {
global $wgOut;
- if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
+ if ( Hooks::run( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
$content1 = $this->toEditContent( $this->textbox1 );
@@ -3292,20 +3345,16 @@ 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(),
- $attrs,
+ Html::buttonAttributes( $attrs, array( 'mw-ui-quiet' ) ),
$cancelParams
);
}
@@ -3401,8 +3450,6 @@ HTML
global $wgOut, $wgUser, $wgRawHtml, $wgLang;
global $wgAllowUserCss, $wgAllowUserJs;
- wfProfileIn( __METHOD__ );
-
if ( $wgRawHtml && !$this->mTokenOk ) {
// Could be an offsite preview attempt. This is very unsafe if
// HTML is enabled, as it could be an attack.
@@ -3414,7 +3461,6 @@ HTML
$parsedNote = $wgOut->parse( "<div class='previewnote'>" .
wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
}
- wfProfileOut( __METHOD__ );
return $parsedNote;
}
@@ -3424,11 +3470,10 @@ HTML
$content = $this->toEditContent( $this->textbox1 );
$previewHTML = '';
- if ( !wfRunHooks(
+ if ( !Hooks::run(
'AlternateEditPreview',
array( $this, &$content, &$previewHTML, &$this->mParserOutput ) )
) {
- wfProfileOut( __METHOD__ );
return $previewHTML;
}
@@ -3449,7 +3494,6 @@ HTML
}
$parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
- $parserOptions->setEditSection( false );
$parserOptions->setIsPreview( true );
$parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
@@ -3494,20 +3538,26 @@ HTML
$hook_args = array( $this, &$content );
ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
- wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
+ Hooks::run( 'EditPageGetPreviewContent', $hook_args );
$parserOptions->enableLimitReport();
# 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
+ $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+ $scopedCallback = $parserOptions->setupFakeRevision(
+ $this->mTitle, $pstContent, $wgUser );
+ $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
+
+ # Try to stash the edit for the final submission step
+ # @todo: different date format preferences cause cache misses
+ ApiStashEdit::stashEditFromPreview(
+ $this->getArticle(), $content, $pstContent,
+ $parserOutput, $parserOptions, $parserOptions, wfTimestampNow()
);
+ $parserOutput->setEditSectionTokens( false ); // no section edit links
$previewHTML = $parserOutput->getText();
$this->mParserOutput = $parserOutput;
$wgOut->addParserOutputMetadata( $parserOutput );
@@ -3542,7 +3592,6 @@ HTML
'class' => 'mw-content-' . $pageViewLang->getDir() );
$previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
- wfProfileOut( __METHOD__ );
return $previewhead . $previewHTML . $this->previewTextAfterContent;
}
@@ -3660,7 +3709,7 @@ HTML
)
);
- $script = 'mw.loader.using("mediawiki.action.edit", function() {';
+ $script = 'mw.loader.using("mediawiki.toolbar", function () {';
foreach ( $toolarray as $tool ) {
if ( !$tool ) {
continue;
@@ -3680,21 +3729,19 @@ HTML
$tool['id'],
);
- $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
+ $script .= Xml::encodeJsCall(
+ 'mw.toolbar.addButton',
+ $params,
+ ResourceLoader::inDebugMode()
+ );
}
- // This used to be called on DOMReady from mediawiki.action.edit, which
- // ended up causing race conditions with the setup code above.
- $script .= "\n" .
- "// Create button bar\n" .
- "$(function() { mw.toolbar.init(); } );\n";
-
$script .= '});';
$wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
$toolbar = '<div id="toolbar"></div>';
- wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
+ Hooks::run( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
return $toolbar;
}
@@ -3761,7 +3808,7 @@ HTML
$checkboxes['watch'] = $watchThisHtml;
}
}
- wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
+ Hooks::run( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
return $checkboxes;
}
@@ -3774,83 +3821,39 @@ HTML
* @return array
*/
public function getEditButtons( &$tabindex ) {
- global $wgUseMediaWikiUIEverywhere;
-
$buttons = array();
$attribs = array(
'id' => 'wpSave',
'name' => 'wpSave',
- 'type' => 'submit',
'tabindex' => ++$tabindex,
- 'value' => wfMessage( 'savearticle' )->text(),
) + Linker::tooltipAndAccesskeyAttribs( 'save' );
- if ( $wgUseMediaWikiUIEverywhere ) {
- $attribs['class'] = 'mw-ui-button mw-ui-constructive';
- }
- $buttons['save'] = Xml::element( 'input', $attribs, '' );
+ $buttons['save'] = Html::submitButton( wfMessage( 'savearticle' )->text(),
+ $attribs, array( 'mw-ui-constructive' ) );
++$tabindex; // use the same for preview and live preview
$attribs = array(
'id' => 'wpPreview',
'name' => 'wpPreview',
- 'type' => 'submit',
'tabindex' => $tabindex,
- 'value' => wfMessage( 'showpreview' )->text(),
) + Linker::tooltipAndAccesskeyAttribs( 'preview' );
- if ( $wgUseMediaWikiUIEverywhere ) {
- $attribs['class'] = 'mw-ui-button mw-ui-progressive';
- }
- $buttons['preview'] = Xml::element( 'input', $attribs, '' );
+ $buttons['preview'] = Html::submitButton( wfMessage( 'showpreview' )->text(),
+ $attribs );
$buttons['live'] = '';
$attribs = array(
'id' => 'wpDiff',
'name' => 'wpDiff',
- 'type' => 'submit',
'tabindex' => ++$tabindex,
- 'value' => wfMessage( 'showdiff' )->text(),
) + Linker::tooltipAndAccesskeyAttribs( 'diff' );
- if ( $wgUseMediaWikiUIEverywhere ) {
- $attribs['class'] = 'mw-ui-button mw-ui-progressive';
- }
- $buttons['diff'] = Xml::element( 'input', $attribs, '' );
+ $buttons['diff'] = Html::submitButton( wfMessage( 'showdiff' )->text(),
+ $attribs );
- wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
+ Hooks::run( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
return $buttons;
}
/**
- * Output preview text only. This can be sucked into the edit page
- * via JavaScript, and saves the server time rendering the skin as
- * well as theoretically being more robust on the client (doesn't
- * disturb the edit box's undo history, won't eat your text on
- * failure, etc).
- *
- * @todo This doesn't include category or interlanguage links.
- * Would need to enhance it a bit, "<s>maybe wrap them in XML
- * or something...</s>" that might also require more skin
- * initialization, so check whether that's a problem.
- */
- function livePreview() {
- global $wgOut;
- $wgOut->disable();
- header( 'Content-type: text/xml; charset=utf-8' );
- header( 'Cache-control: no-cache' );
-
- $previewText = $this->getPreviewText();
- #$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 )
- );
- echo $s;
- }
-
- /**
* Creates a basic error page which informs the user that
* they have attempted to edit a nonexistent section.
*/
@@ -3860,7 +3863,7 @@ HTML
$wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
$res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
- wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
+ Hooks::run( 'EditPageNoSuchSection', array( &$this, &$res ) );
$wgOut->addHTML( $res );
$wgOut->returnToMain( false, $this->mTitle );
@@ -4018,9 +4021,9 @@ HTML
// Do some sanity checks. These aren't needed for reversibility,
// but should help keep the breakage down if the editor
// breaks one of the entities whilst editing.
- if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
+ if ( ( substr( $invalue, $i, 1 ) == ";" ) && ( strlen( $hexstring ) <= 6 ) ) {
$codepoint = hexdec( $hexstring );
- $result .= codepointToUtf8( $codepoint );
+ $result .= UtfNormal\Utils::codepointToUtf8( $codepoint );
} else {
$result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
}
diff --git a/includes/Export.php b/includes/Export.php
index 84f5c60c..4600feb5 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -69,7 +69,7 @@ class WikiExporter {
* @return string
*/
public static function schemaVersion() {
- return "0.9";
+ return "0.10";
}
/**
@@ -213,7 +213,6 @@ class WikiExporter {
* @param array $cond
*/
protected function do_list_authors( $cond ) {
- wfProfileIn( __METHOD__ );
$this->author_list = "<contributors>";
// rev_deleted
@@ -239,7 +238,6 @@ class WikiExporter {
"</contributor>";
}
$this->author_list .= "</contributors>";
- wfProfileOut( __METHOD__ );
}
/**
@@ -248,7 +246,6 @@ class WikiExporter {
* @throws Exception
*/
protected function dumpFrom( $cond = '' ) {
- wfProfileIn( __METHOD__ );
# For logging dumps...
if ( $this->history & self::LOGS ) {
$where = array( 'user_id = log_user' );
@@ -304,7 +301,6 @@ class WikiExporter {
}
// Inform caller about problem
- wfProfileOut( __METHOD__ );
throw $e;
}
# For page dumps...
@@ -348,8 +344,7 @@ class WikiExporter {
# Default JOIN, to be overridden...
$join['revision'] = array( 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' );
# One, and only one hook should set this, and return false
- if ( wfRunHooks( 'WikiExporter::dumpStableQuery', array( &$tables, &$opts, &$join ) ) ) {
- wfProfileOut( __METHOD__ );
+ if ( Hooks::run( 'WikiExporter::dumpStableQuery', array( &$tables, &$opts, &$join ) ) ) {
throw new MWException( __METHOD__ . " given invalid history dump type." );
}
} elseif ( $this->history & WikiExporter::RANGE ) {
@@ -358,7 +353,6 @@ class WikiExporter {
$opts['ORDER BY'] = array( 'rev_page ASC', 'rev_id ASC' );
} else {
# Unknown history specification parameter?
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . " given invalid history dump type." );
}
# Query optimization hacks
@@ -378,7 +372,7 @@ class WikiExporter {
$result = null; // Assuring $result is not undefined, if exception occurs early
try {
- wfRunHooks( 'ModifyExportQuery',
+ Hooks::run( 'ModifyExportQuery',
array( $this->db, &$tables, &$cond, &$opts, &$join ) );
# Do the query!
@@ -417,7 +411,6 @@ class WikiExporter {
throw $e;
}
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -480,16 +473,6 @@ class WikiExporter {
*/
class XmlDumpWriter {
/**
- * Returns the export schema version.
- * @deprecated since 1.20; use WikiExporter::schemaVersion() instead
- * @return string
- */
- function schemaVersion() {
- wfDeprecated( __METHOD__, '1.20' );
- return WikiExporter::schemaVersion();
- }
-
- /**
* Opens the XML output stream's root "<mediawiki>" element.
* This does not include an xml directive, so is safe to include
* as a subelement in a larger XML stream. Namespace and XML Schema
@@ -637,7 +620,7 @@ class XmlDumpWriter {
strval( $row->page_restrictions ) ) . "\n";
}
- wfRunHooks( 'XmlDumpWriterOpenPage', array( $this, &$out, $row, $title ) );
+ Hooks::run( 'XmlDumpWriterOpenPage', array( $this, &$out, $row, $title ) );
return $out;
}
@@ -661,7 +644,6 @@ class XmlDumpWriter {
* @access private
*/
function writeRevision( $row ) {
- wfProfileIn( __METHOD__ );
$out = " <revision>\n";
$out .= " " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n";
@@ -703,6 +685,9 @@ class XmlDumpWriter {
$content_format = $content_handler->getDefaultFormat();
}
+ $out .= " " . Xml::element( 'model', null, strval( $content_model ) ) . "\n";
+ $out .= " " . Xml::element( 'format', null, strval( $content_format ) ) . "\n";
+
$text = '';
if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_TEXT ) ) {
$out .= " " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
@@ -729,14 +714,10 @@ class XmlDumpWriter {
$out .= " <sha1/>\n";
}
- $out .= " " . Xml::element( 'model', null, strval( $content_model ) ) . "\n";
- $out .= " " . Xml::element( 'format', null, strval( $content_format ) ) . "\n";
-
- wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
+ Hooks::run( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) );
$out .= " </revision>\n";
- wfProfileOut( __METHOD__ );
return $out;
}
@@ -749,7 +730,6 @@ class XmlDumpWriter {
* @access private
*/
function writeLogItem( $row ) {
- wfProfileIn( __METHOD__ );
$out = " <logitem>\n";
$out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n";
@@ -783,7 +763,6 @@ class XmlDumpWriter {
$out .= " </logitem>\n";
- wfProfileOut( __METHOD__ );
return $out;
}
diff --git a/includes/Feed.php b/includes/Feed.php
index 2fdfa424..600b136d 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -92,7 +92,7 @@ class FeedItem {
*/
public function getUniqueId() {
if ( $this->uniqueId ) {
- return $this->xmlEncode( $this->uniqueId );
+ return $this->xmlEncode( wfExpandUrl( $this->uniqueId, PROTO_CURRENT ) );
}
}
@@ -184,7 +184,8 @@ class FeedItem {
}
/**
- * @todo document (needs one-sentence top-level class description).
+ * Class to support the outputting of syndication feeds in Atom and RSS format.
+ *
* @ingroup Feed
*/
abstract class ChannelFeed extends FeedItem {
@@ -338,13 +339,14 @@ class RSSFeed extends ChannelFeed {
*/
class AtomFeed extends ChannelFeed {
/**
- * @todo document
- * @param string|int $ts
+ * Format a date given timestamp.
+ *
+ * @param string|int $timestamp
* @return string
*/
- function formatTime( $ts ) {
+ function formatTime( $timestamp ) {
// need to use RFC 822 time format at least for rss2.0
- return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $ts ) );
+ return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $timestamp ) );
}
/**
diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php
index 6937c32d..a01d6420 100644
--- a/includes/FeedUtils.php
+++ b/includes/FeedUtils.php
@@ -106,7 +106,6 @@ class FeedUtils {
$comment, $actiontext = ''
) {
global $wgFeedDiffCutoff, $wgLang;
- wfProfileIn( __METHOD__ );
// log entries
$completeText = '<p>' . implode( ' ',
@@ -124,12 +123,10 @@ class FeedUtils {
// Can't diff special pages, unreadable pages or pages with no new revision
// to compare against: just return the text.
if ( $title->getNamespace() < 0 || $accErrors || !$newid ) {
- wfProfileOut( __METHOD__ );
return $completeText;
}
if ( $oldid ) {
- wfProfileIn( __METHOD__ . "-dodiff" );
#$diffText = $de->getDiff( wfMessage( 'revisionasof',
# $wgLang->timeanddate( $timestamp ),
@@ -167,10 +164,9 @@ class FeedUtils {
$diffText = "<p>Can't load revision $newid</p>";
} else {
// Diff output fine, clean up any illegal UTF-8
- $diffText = UtfNormal::cleanUp( $diffText );
+ $diffText = UtfNormal\Validator::cleanUp( $diffText );
$diffText = self::applyDiffStyle( $diffText );
}
- wfProfileOut( __METHOD__ . "-dodiff" );
} else {
$rev = Revision::newFromId( $newid );
if ( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) {
@@ -208,7 +204,6 @@ class FeedUtils {
}
$completeText .= $diffText;
- wfProfileOut( __METHOD__ );
return $completeText;
}
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index b4e24581..c1d14db0 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -201,7 +201,7 @@ class FileDeleteForm {
$dbw->rollback( __METHOD__ );
}
}
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// Rollback before returning to prevent UI from displaying
// incorrect "View or restore N deleted edits?"
$dbw->rollback( __METHOD__ );
@@ -210,7 +210,7 @@ class FileDeleteForm {
}
if ( $status->isOK() ) {
- wfRunHooks( 'FileDeleteComplete', array( &$file, &$oldimage, &$page, &$user, &$reason ) );
+ Hooks::run( 'FileDeleteComplete', array( &$file, &$oldimage, &$page, &$user, &$reason ) );
}
return $status;
diff --git a/includes/GitInfo.php b/includes/GitInfo.php
index 7052820e..fb298cfe 100644
--- a/includes/GitInfo.php
+++ b/includes/GitInfo.php
@@ -392,7 +392,7 @@ class GitInfo {
if ( self::$viewers === false ) {
self::$viewers = $wgGitRepositoryViewers;
- wfRunHooks( 'GitViewers', array( &self::$viewers ) );
+ Hooks::run( 'GitViewers', array( &self::$viewers ) );
}
return self::$viewers;
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 27f7cacb..240cb97b 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -24,13 +24,17 @@ if ( !defined( 'MEDIAWIKI' ) ) {
die( "This file is part of MediaWiki, it is not a valid entry point" );
}
+use Liuggio\StatsdClient\StatsdClient;
+use Liuggio\StatsdClient\Sender\SocketSender;
+use MediaWiki\Logger\LoggerFactory;
+
// Hide compatibility functions from Doxygen
/// @cond
/**
* Compatibility functions
*
- * We support PHP 5.3.2 and up.
+ * We support PHP 5.3.3 and up.
* Re-implementations of newer functions or functions in non-standard
* PHP extensions may be included here.
*/
@@ -161,6 +165,72 @@ if ( !function_exists( 'hash_equals' ) ) {
/// @endcond
/**
+ * Load an extension
+ *
+ * This queues an extension to be loaded through
+ * the ExtensionRegistry system.
+ *
+ * @param string $ext Name of the extension to load
+ * @param string|null $path Absolute path of where to find the extension.json file
+ */
+function wfLoadExtension( $ext, $path = null ) {
+ if ( !$path ) {
+ global $wgExtensionDirectory;
+ $path = "$wgExtensionDirectory/$ext/extension.json";
+ }
+ ExtensionRegistry::getInstance()->queue( $path );
+}
+
+/**
+ * Load multiple extensions at once
+ *
+ * Same as wfLoadExtension, but more efficient if you
+ * are loading multiple extensions.
+ *
+ * If you want to specify custom paths, you should interact with
+ * ExtensionRegistry directly.
+ *
+ * @see wfLoadExtension
+ * @param string[] $exts Array of extension names to load
+ */
+function wfLoadExtensions( array $exts ) {
+ global $wgExtensionDirectory;
+ $registry = ExtensionRegistry::getInstance();
+ foreach ( $exts as $ext ) {
+ $registry->queue( "$wgExtensionDirectory/$ext/extension.json" );
+ }
+}
+
+/**
+ * Load a skin
+ *
+ * @see wfLoadExtension
+ * @param string $skin Name of the extension to load
+ * @param string|null $path Absolute path of where to find the skin.json file
+ */
+function wfLoadSkin( $skin, $path = null ) {
+ if ( !$path ) {
+ global $wgStyleDirectory;
+ $path = "$wgStyleDirectory/$skin/skin.json";
+ }
+ ExtensionRegistry::getInstance()->queue( $path );
+}
+
+/**
+ * Load multiple skins at once
+ *
+ * @see wfLoadExtensions
+ * @param string[] $skins Array of extension names to load
+ */
+function wfLoadSkins( array $skins ) {
+ global $wgStyleDirectory;
+ $registry = ExtensionRegistry::getInstance();
+ foreach ( $skins as $skin ) {
+ $registry->queue( "$wgStyleDirectory/$skin/skin.json" );
+ }
+}
+
+/**
* Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
* @param array $a
* @param array $b
@@ -950,44 +1020,40 @@ function wfMatchesDomainList( $url, $domains ) {
* $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.
*
+ * @since 1.25 support for additional context data
+ *
* @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'
+ * @param string|bool $dest Unused
+ * @param array $context Additional logging context data
*/
-function wfDebug( $text, $dest = 'all' ) {
- global $wgDebugLogFile, $wgDebugRawPage, $wgDebugLogPrefix;
+function wfDebug( $text, $dest = 'all', array $context = array() ) {
+ global $wgDebugRawPage, $wgDebugLogPrefix;
+ global $wgDebugTimestamps, $wgRequestTime;
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';
- }
+ $text = trim( $text );
- $timer = wfDebugTimer();
- if ( $timer !== '' ) {
- $text = preg_replace( '/[^\n]/', $timer . '\0', $text, 1 );
+ // Inline logic from deprecated wfDebugTimer()
+ if ( $wgDebugTimestamps ) {
+ $context['seconds_elapsed'] = sprintf(
+ '%6.4f',
+ microtime( true ) - $wgRequestTime
+ );
+ $context['memory_used'] = sprintf(
+ '%5.1fM',
+ ( memory_get_usage( true ) / ( 1024 * 1024 ) )
+ );
}
- if ( $dest === 'all' ) {
- MWDebug::debugMsg( $text );
+ if ( $wgDebugLogPrefix !== '' ) {
+ $context['prefix'] = $wgDebugLogPrefix;
}
- 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 );
- $text = $wgDebugLogPrefix . $text;
- wfErrorLog( $text, $wgDebugLogFile );
- }
+ $logger = LoggerFactory::getInstance( 'wfDebug' );
+ $logger->debug( $text, $context );
}
/**
@@ -1016,11 +1082,14 @@ function wfIsDebugRawPage() {
/**
* Get microsecond timestamps for debug logs
*
+ * @deprecated since 1.25
* @return string
*/
function wfDebugTimer() {
global $wgDebugTimestamps, $wgRequestTime;
+ wfDeprecated( __METHOD__, '1.25' );
+
if ( !$wgDebugTimestamps ) {
return '';
}
@@ -1046,30 +1115,34 @@ function wfDebugMem( $exact = false ) {
}
/**
- * Send a line to a supplementary debug log file, if configured, or main debug log if not.
- * 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.
+ * Send a line to a supplementary debug log file, if configured, or main debug
+ * log if not.
+ *
+ * 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. Sampled log events
+ * will be emitted with a 1 in N random chance.
*
* @since 1.23 support for sampling log messages via $wgDebugLogGroups.
+ * @since 1.25 support for additional context data
+ * @since 1.25 sample behavior dependent on configured $wgMWLoggerDefaultSpi
*
* @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
+ * - 'private': only to the specific 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'
+ * @param array $context Additional logging context data
*/
-function wfDebugLog( $logGroup, $text, $dest = 'all' ) {
- global $wgDebugLogGroups;
-
- $text = trim( $text ) . "\n";
-
+function wfDebugLog(
+ $logGroup, $text, $dest = 'all', array $context = array()
+) {
// Turn $dest into a string if it's a boolean (for b/c)
if ( $dest === true ) {
$dest = 'all';
@@ -1077,66 +1150,24 @@ function wfDebugLog( $logGroup, $text, $dest = 'all' ) {
$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 );
- }
+ $text = trim( $text );
- $time = wfTimestamp( TS_DB );
- $wiki = wfWikiID();
- $host = wfHostname();
- wfErrorLog( "$time $host $wiki: $text", $destination );
+ $logger = LoggerFactory::getInstance( $logGroup );
+ $context['private'] = ( $dest === 'private' );
+ $logger->info( $text, $context );
}
/**
* Log for database errors
*
+ * @since 1.25 support for additional context data
+ *
* @param string $text Database error message.
+ * @param array $context Additional logging context data
*/
-function wfLogDBError( $text ) {
- global $wgDBerrorLog, $wgDBerrorLogTZ;
- static $logDBErrorTimeZoneObject = null;
-
- if ( $wgDBerrorLog ) {
- $host = wfHostname();
- $wiki = wfWikiID();
-
- if ( $wgDBerrorLogTZ && !$logDBErrorTimeZoneObject ) {
- $logDBErrorTimeZoneObject = new DateTimeZone( $wgDBerrorLogTZ );
- }
-
- // Workaround for https://bugs.php.net/bug.php?id=52063
- // Can be removed when min PHP > 5.3.2
- if ( $logDBErrorTimeZoneObject === null ) {
- $d = date_create( "now" );
- } else {
- $d = date_create( "now", $logDBErrorTimeZoneObject );
- }
-
- $date = $d->format( 'D M j G:i:s T Y' );
-
- $text = "$date\t$host\t$wiki\t" . trim( $text ) . "\n";
- wfErrorLog( $text, $wgDBerrorLog );
- }
+function wfLogDBError( $text, array $context = array() ) {
+ $logger = LoggerFactory::getInstance( 'wfLogDBError' );
+ $logger->error( trim( $text ), $context );
}
/**
@@ -1188,138 +1219,93 @@ 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.
+ * @since 1.25 support for additional context data
*
* @param string $text
* @param string $file Filename
+ * @param array $context Additional logging context data
* @throws MWException
+ * @deprecated since 1.25 Use MediaWiki\Logger\LegacyLogger::emit or UDPTransport
*/
-function wfErrorLog( $text, $file ) {
- if ( substr( $file, 0, 4 ) == 'udp:' ) {
- # Needs the sockets extension
- if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) {
- // IPv6 bracketed host
- $host = $m[2];
- $port = intval( $m[3] );
- $prefix = isset( $m[4] ) ? $m[4] : false;
- $domain = AF_INET6;
- } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) {
- $host = $m[2];
- if ( !IP::isIPv4( $host ) ) {
- $host = gethostbyname( $host );
- }
- $port = intval( $m[3] );
- $prefix = isset( $m[4] ) ? $m[4] : false;
- $domain = AF_INET;
- } else {
- throw new MWException( __METHOD__ . ': Invalid UDP specification' );
- }
-
- // Clean it up for the multiplexer
- if ( strval( $prefix ) !== '' ) {
- $text = preg_replace( '/^/m', $prefix . ' ', $text );
-
- // Limit to 64KB
- if ( strlen( $text ) > 65506 ) {
- $text = substr( $text, 0, 65506 );
- }
-
- if ( substr( $text, -1 ) != "\n" ) {
- $text .= "\n";
- }
- } elseif ( strlen( $text ) > 65507 ) {
- $text = substr( $text, 0, 65507 );
- }
-
- $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
- if ( !$sock ) {
- return;
- }
-
- socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
- socket_close( $sock );
- } else {
- wfSuppressWarnings();
- $exists = file_exists( $file );
- $size = $exists ? filesize( $file ) : false;
- if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
- file_put_contents( $file, $text, FILE_APPEND );
- }
- wfRestoreWarnings();
- }
+function wfErrorLog( $text, $file, array $context = array() ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ $logger = LoggerFactory::getInstance( 'wfErrorLog' );
+ $context['destination'] = $file;
+ $logger->info( trim( $text ), $context );
}
/**
* @todo document
*/
function wfLogProfilingData() {
- global $wgRequestTime, $wgDebugLogFile, $wgDebugLogGroups, $wgDebugRawPage;
- global $wgProfileLimit, $wgUser, $wgRequest;
+ global $wgDebugLogGroups, $wgDebugRawPage;
- StatCounter::singleton()->flush();
+ $context = RequestContext::getMain();
+ $request = $context->getRequest();
$profiler = Profiler::instance();
+ $profiler->setContext( $context );
+ $profiler->logData();
- # Profiling must actually be enabled...
- if ( $profiler->isStub() ) {
- return;
+ $config = $context->getConfig();
+ if ( $config->has( 'StatsdServer' ) ) {
+ $statsdServer = explode( ':', $config->get( 'StatsdServer' ) );
+ $statsdHost = $statsdServer[0];
+ $statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125;
+ $statsdSender = new SocketSender( $statsdHost, $statsdPort );
+ $statsdClient = new StatsdClient( $statsdSender );
+ $statsdClient->send( $context->getStats()->getBuffer() );
}
- // Get total page request time and only show pages that longer than
- // $wgProfileLimit time (default is 0)
- $elapsed = microtime( true ) - $wgRequestTime;
- if ( $elapsed <= $wgProfileLimit ) {
+ # Profiling must actually be enabled...
+ if ( $profiler instanceof ProfilerStub ) {
return;
}
- $profiler->logData();
-
- // Check whether this should be logged in the debug file.
if ( isset( $wgDebugLogGroups['profileoutput'] )
&& $wgDebugLogGroups['profileoutput'] === false
) {
- // Explicitely disabled
- return;
- }
- if ( !isset( $wgDebugLogGroups['profileoutput'] ) && $wgDebugLogFile == '' ) {
- // Logging not enabled; no point going further
+ // Explicitly disabled
return;
}
if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
return;
}
- $forward = '';
+ $ctx = array( 'elapsed' => $request->getElapsedTime() );
if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
- $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
+ $ctx['forwarded_for'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
- $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP'];
+ $ctx['client_ip'] = $_SERVER['HTTP_CLIENT_IP'];
}
if ( !empty( $_SERVER['HTTP_FROM'] ) ) {
- $forward .= ' from ' . $_SERVER['HTTP_FROM'];
+ $ctx['from'] = $_SERVER['HTTP_FROM'];
}
- if ( $forward ) {
- $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})";
+ if ( isset( $ctx['forwarded_for'] ) ||
+ isset( $ctx['client_ip'] ) ||
+ isset( $ctx['from'] ) ) {
+ $ctx['proxy'] = $_SERVER['REMOTE_ADDR'];
}
+
// Don't load $wgUser at this late stage just for statistics purposes
- // @todo FIXME: We can detect some anons even if it is not loaded. See User::getId()
- if ( $wgUser->isItemLoaded( 'id' ) && $wgUser->isAnon() ) {
- $forward .= ' anon';
- }
+ // @todo FIXME: We can detect some anons even if it is not loaded.
+ // See User::getId()
+ $user = $context->getUser();
+ $ctx['anon'] = $user->isItemLoaded( 'id' ) && $user->isAnon();
// Command line script uses a FauxRequest object which does not have
// any knowledge about an URL and throw an exception instead.
try {
- $requestUrl = $wgRequest->getRequestURL();
- } catch ( MWException $e ) {
- $requestUrl = 'n/a';
+ $ctx['url'] = urldecode( $request->getRequestURL() );
+ } catch ( Exception $ignored ) {
+ // no-op
}
- $log = sprintf( "%s\t%04.3f\t%s\n",
- gmdate( 'YmdHis' ), $elapsed,
- urldecode( $requestUrl . $forward ) );
+ $ctx['output'] = $profiler->getOutput();
- wfDebugLog( 'profileoutput', $log . $profiler->getOutput() );
+ $log = LoggerFactory::getInstance( 'profileoutput' );
+ $log->info( "Elapsed: {elapsed}; URL: <{url}>\n{output}", $ctx );
}
/**
@@ -1330,7 +1316,8 @@ function wfLogProfilingData() {
* @return void
*/
function wfIncrStats( $key, $count = 1 ) {
- StatCounter::singleton()->incr( $key, $count );
+ $stats = RequestContext::getMain()->getStats();
+ $stats->updateCount( $key, $count );
}
/**
@@ -1573,10 +1560,8 @@ function wfMsgForContentNoTrans( $key ) {
function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) {
wfDeprecated( __METHOD__, '1.21' );
- wfProfileIn( __METHOD__ );
$message = wfMsgGetKey( $key, $useDB, $forContent, $transform );
$message = wfMsgReplaceArgs( $message, $args );
- wfProfileOut( __METHOD__ );
return $message;
}
@@ -1595,7 +1580,7 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform
function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) {
wfDeprecated( __METHOD__, '1.21' );
- wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
+ Hooks::run( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
$cache = MessageCache::singleton();
$message = $cache->get( $key, $useDB, $langCode );
@@ -1710,15 +1695,15 @@ function wfMsgExt( $key, $options ) {
array_shift( $args );
array_shift( $args );
$options = (array)$options;
+ $validOptions = array( 'parse', 'parseinline', 'escape', 'escapenoentities', 'replaceafter',
+ 'parsemag', 'content' );
foreach ( $options as $arrayKey => $option ) {
if ( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) {
- # An unknown index, neither numeric nor "language"
+ // An unknown index, neither numeric nor "language"
wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING );
- } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option,
- array( 'parse', 'parseinline', 'escape', 'escapenoentities',
- 'replaceafter', 'parsemag', 'content' ) ) ) {
- # A numeric index with unknown value
+ } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option, $validOptions ) ) {
+ // A numeric index with unknown value
wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING );
}
}
@@ -1879,52 +1864,37 @@ function wfDebugBacktrace( $limit = 0 ) {
/**
* Get a debug backtrace as a string
*
+ * @param bool|null $raw If true, the return value is plain text. If false, HTML.
+ * Defaults to $wgCommandLineMode if unset.
* @return string
+ * @since 1.25 Supports $raw parameter.
*/
-function wfBacktrace() {
+function wfBacktrace( $raw = null ) {
global $wgCommandLineMode;
- if ( $wgCommandLineMode ) {
- $msg = '';
- } else {
- $msg = "<ul>\n";
+ if ( $raw === null ) {
+ $raw = $wgCommandLineMode;
}
- $backtrace = wfDebugBacktrace();
- foreach ( $backtrace as $call ) {
- if ( isset( $call['file'] ) ) {
- $f = explode( DIRECTORY_SEPARATOR, $call['file'] );
- $file = $f[count( $f ) - 1];
- } else {
- $file = '-';
- }
- if ( isset( $call['line'] ) ) {
- $line = $call['line'];
- } else {
- $line = '-';
- }
- if ( $wgCommandLineMode ) {
- $msg .= "$file line $line calls ";
- } else {
- $msg .= '<li>' . $file . ' line ' . $line . ' calls ';
- }
- if ( !empty( $call['class'] ) ) {
- $msg .= $call['class'] . $call['type'];
- }
- $msg .= $call['function'] . '()';
- if ( $wgCommandLineMode ) {
- $msg .= "\n";
- } else {
- $msg .= "</li>\n";
- }
- }
- if ( $wgCommandLineMode ) {
- $msg .= "\n";
+ if ( $raw ) {
+ $frameFormat = "%s line %s calls %s()\n";
+ $traceFormat = "%s";
} else {
- $msg .= "</ul>\n";
+ $frameFormat = "<li>%s line %s calls %s()</li>\n";
+ $traceFormat = "<ul>\n%s</ul>\n";
}
- return $msg;
+ $frames = array_map( function ( $frame ) use ( $frameFormat ) {
+ $file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-';
+ $line = isset( $frame['line'] ) ? $frame['line'] : '-';
+ $call = $frame['function'];
+ if ( !empty( $frame['class'] ) ) {
+ $call = $frame['class'] . $frame['type'] . $call;
+ }
+ return sprintf( $frameFormat, $file, $line, $call );
+ }, wfDebugBacktrace() );
+
+ return sprintf( $traceFormat, implode( '', $frames ) );
}
/**
@@ -2148,10 +2118,12 @@ function wfVarDump( $var ) {
*/
function wfHttpError( $code, $label, $desc ) {
global $wgOut;
- $wgOut->disable();
header( "HTTP/1.0 $code $label" );
header( "Status: $code $label" );
- $wgOut->sendCacheControl();
+ if ( $wgOut ) {
+ $wgOut->disable();
+ $wgOut->sendCacheControl();
+ }
header( 'Content-type: text/html; charset=utf-8' );
print "<!doctype html>" .
@@ -2496,20 +2468,6 @@ function wfIsHHVM() {
}
/**
- * Swap two variables
- *
- * @deprecated since 1.24
- * @param mixed $x
- * @param mixed $y
- */
-function swap( &$x, &$y ) {
- wfDeprecated( __FUNCTION__, '1.24' );
- $z = $x;
- $x = $y;
- $y = $z;
-}
-
-/**
* Tries to get the system directory for temporary files. First
* $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP
* environment variables are then checked in sequence, and if none are
@@ -2659,13 +2617,19 @@ function wfIniGetBool( $setting ) {
* Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to
* earlier distro releases of PHP)
*
- * @param string $args,...
+ * @param string ... strings to escape and glue together, or a single array of strings parameter
* @return string
*/
function wfEscapeShellArg( /*...*/ ) {
wfInitShellLocale();
$args = func_get_args();
+ if ( count( $args ) === 1 && is_array( reset( $args ) ) ) {
+ // If only one argument has been passed, and that argument is an array,
+ // treat it as a list of arguments
+ $args = reset( $args );
+ }
+
$first = true;
$retVal = '';
foreach ( $args as $arg ) {
@@ -2756,6 +2720,8 @@ function wfShellExecDisabled() {
* @param array $options Array of options:
* - duplicateStderr: Set this to true to duplicate stderr to stdout,
* including errors from limit.sh
+ * - profileMethod: By default this function will profile based on the calling
+ * method. Set this to a string for an alternative method to profile from
*
* @return string Collected stdout as a string
*/
@@ -2774,6 +2740,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(),
}
$includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
+ $profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller();
wfInitShellLocale();
@@ -2795,12 +2762,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(),
}
}
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 = wfEscapeShellArg( $cmd );
}
$cmd = $envcmd . $cmd;
@@ -2847,6 +2809,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(),
$desc[3] = array( 'pipe', 'w' );
}
$pipes = null;
+ $scoped = Profiler::instance()->scopedProfileIn( __FUNCTION__ . '-' . $profileMethod );
$proc = proc_open( $cmd, $desc, $pipes );
if ( !$proc ) {
wfDebugLog( 'exec', "proc_open() failed: $cmd" );
@@ -2986,7 +2949,9 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(),
* function, as all the arguments to wfShellExec can become unwieldy.
*
* @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
- * @param string $cmd Command line, properly escaped for shell.
+ * @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)
* @param array $environ Optional environment variables which should be
@@ -2996,7 +2961,8 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(),
* @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 ) );
+ return wfShellExec( $cmd, $retval, $environ, $limits,
+ array( 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ) );
}
/**
@@ -3017,15 +2983,6 @@ function wfInitShellLocale() {
}
/**
- * Alias to wfShellWikiCmd()
- *
- * @see wfShellWikiCmd()
- */
-function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) {
- return wfShellWikiCmd( $script, $parameters, $options );
-}
-
-/**
* 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").
@@ -3041,14 +2998,14 @@ function wfShellWikiCmd( $script, array $parameters = array(), array $options =
global $wgPhpCli;
// Give site config file a chance to run the script in a wrapper.
// The caller may likely want to call wfBasename() on $script.
- wfRunHooks( 'wfShellWikiCmd', array( &$script, &$parameters, &$options ) );
+ Hooks::run( 'wfShellWikiCmd', array( &$script, &$parameters, &$options ) );
$cmd = isset( $options['php'] ) ? array( $options['php'] ) : array( $wgPhpCli );
if ( isset( $options['wrapper'] ) ) {
$cmd[] = $options['wrapper'];
}
$cmd[] = $script;
// Escape each parameter for shell
- return implode( " ", array_map( 'wfEscapeShellArg', array_merge( $cmd, $parameters ) ) );
+ return wfEscapeShellArg( array_merge( $cmd, $parameters ) );
}
/**
@@ -3093,10 +3050,8 @@ function wfMerge( $old, $mine, $yours, &$result ) {
fclose( $yourtextFile );
# Check for a conflict
- $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a --overlap-only ' .
- wfEscapeShellArg( $mytextName ) . ' ' .
- wfEscapeShellArg( $oldtextName ) . ' ' .
- wfEscapeShellArg( $yourtextName );
+ $cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName,
+ $oldtextName, $yourtextName );
$handle = popen( $cmd, 'r' );
if ( fgets( $handle, 1024 ) ) {
@@ -3107,8 +3062,8 @@ function wfMerge( $old, $mine, $yours, &$result ) {
pclose( $handle );
# Merge differences
- $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a -e --merge ' .
- wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
+ $cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName,
+ $oldtextName, $yourtextName );
$handle = popen( $cmd, 'r' );
$result = '';
do {
@@ -3132,7 +3087,9 @@ function wfMerge( $old, $mine, $yours, &$result ) {
/**
* Returns unified plain-text diff of two texts.
- * Useful for machine processing of diffs.
+ * "Useful" for machine processing of diffs.
+ *
+ * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly
*
* @param string $before The text before the changes.
* @param string $after The text after the changes.
@@ -3172,6 +3129,11 @@ function wfDiff( $before, $after, $params = '-u' ) {
$cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
$h = popen( $cmd, 'r' );
+ if ( !$h ) {
+ unlink( $oldtextName );
+ unlink( $newtextName );
+ throw new Exception( __METHOD__ . '(): popen() failed' );
+ }
$diff = '';
@@ -3493,7 +3455,7 @@ function wfResetSessionID() {
$_SESSION = $tmp;
}
$newSessionId = session_id();
- wfRunHooks( 'ResetSessionID', array( $oldSessionId, $newSessionId ) );
+ Hooks::run( 'ResetSessionID', array( $oldSessionId, $newSessionId ) );
}
/**
@@ -3628,7 +3590,7 @@ function wfSplitWikiID( $wiki ) {
*
* @return DatabaseBase
*/
-function &wfGetDB( $db, $groups = array(), $wiki = false ) {
+function wfGetDB( $db, $groups = array(), $wiki = false ) {
return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki );
}
@@ -3647,7 +3609,7 @@ function wfGetLB( $wiki = false ) {
*
* @return LBFactory
*/
-function &wfGetLBFactory() {
+function wfGetLBFactory() {
return LBFactory::singleton();
}
@@ -3656,19 +3618,7 @@ function &wfGetLBFactory() {
* Shortcut for RepoGroup::singleton()->findFile()
*
* @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
- * 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
- *
+ * @param array $options Associative array of options (see RepoGroup::findFile)
* @return File|bool File, or false if the file does not exist
*/
function wfFindFile( $title, $options = array() ) {
@@ -3763,46 +3713,79 @@ function wfGetNull() {
}
/**
- * Modern version of wfWaitForSlaves(). Instead of looking at replication lag
- * and waiting for it to go down, this waits for the slaves to catch up to the
- * master position. Use this when updating very large numbers of rows, as
- * in maintenance scripts, to avoid causing too much lag. Of course, this is
- * a no-op if there are no slaves.
+ * Waits for the slaves to catch up to the master position
+ *
+ * Use this when updating very large numbers of rows, as in maintenance scripts,
+ * to avoid causing too much lag. Of course, this is a no-op if there are no slaves.
+ *
+ * By default this waits on the main DB cluster of the current wiki.
+ * If $cluster is set to "*" it will wait on all DB clusters, including
+ * external ones. If the lag being waiting on is caused by the code that
+ * does this check, it makes since to use $ifWritesSince, particularly if
+ * cluster is "*", to avoid excess overhead.
+ *
+ * Never call this function after a big DB write that is still in a transaction.
+ * This only makes sense after the possible lag inducing changes were committed.
*
* @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.
+ * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web)
* @return bool Success (able to connect and no timeouts reached)
*/
-function wfWaitForSlaves( $ifWritesSince = false, $wiki = false, $cluster = false ) {
+function wfWaitForSlaves(
+ $ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null
+) {
// B/C: first argument used to be "max seconds of lag"; ignore such values
- $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : false;
+ $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null;
- if ( $cluster !== false ) {
- $lb = wfGetLBFactory()->getExternalLB( $cluster );
- } else {
- $lb = wfGetLB( $wiki );
+ if ( $timeout === null ) {
+ $timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10;
}
- // 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
+ // Figure out which clusters need to be checked
+ $lbs = array();
+ if ( $cluster === '*' ) {
+ wfGetLBFactory()->forEachLB( function ( LoadBalancer $lb ) use ( &$lbs ) {
+ $lbs[] = $lb;
+ } );
+ } elseif ( $cluster !== false ) {
+ $lbs[] = wfGetLBFactory()->getExternalLB( $cluster );
+ } else {
+ $lbs[] = wfGetLB( $wiki );
+ }
+
+ // Get all the master positions of applicable DBs right now.
+ // This can be faster since waiting on one cluster reduces the
+ // time needed to wait on the next clusters.
+ $masterPositions = array_fill( 0, count( $lbs ), false );
+ foreach ( $lbs as $i => $lb ) {
+ // 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() ) {
+ continue; // assume no writes done
+ }
+ // Use the empty string to not trigger selectDB() since the connection
+ // may have been to a server that does not have a DB for the current wiki.
+ $dbw = $lb->getConnection( DB_MASTER, array(), '' );
+ if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) {
+ continue; // no writes since the last wait
+ }
+ $masterPositions[$i] = $dbw->getMasterPos();
}
- $pos = $dbw->getMasterPos();
- // The DBMS may not support getMasterPos() or the whole
- // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
- if ( $pos !== false ) {
- return $lb->waitForAll( $pos, PHP_SAPI === 'cli' ? 86400 : null );
+ }
+
+ $ok = true;
+ foreach ( $lbs as $i => $lb ) {
+ if ( $masterPositions[$i] ) {
+ // The DBMS may not support getMasterPos() or the whole
+ // load balancer might be fake (e.g. $wgAllDBsAreLocalhost).
+ $ok = $lb->waitForAll( $masterPositions[$i], $timeout ) && $ok;
}
}
- return true;
+ return $ok;
}
/**
@@ -3935,7 +3918,7 @@ function wfBCP47( $code ) {
/**
* Get a cache object.
*
- * @param int $inputType Cache type, one the the CACHE_* constants.
+ * @param int $inputType Cache type, one of the CACHE_* constants.
* @return BagOStuff
*/
function wfGetCache( $inputType ) {
@@ -3973,16 +3956,6 @@ function wfGetParserCacheStorage() {
}
/**
- * Get the cache object used by the language converter
- *
- * @return BagOStuff
- */
-function wfGetLangConverterCacheStorage() {
- global $wgLanguageConverterCacheType;
- return ObjectCache::getInstance( $wgLanguageConverterCacheType );
-}
-
-/**
* Call hook functions defined in $wgHooks
*
* @param string $event Event name
@@ -3990,6 +3963,7 @@ function wfGetLangConverterCacheStorage() {
* @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number
*
* @return bool True if no handler aborted the hook
+ * @deprecated 1.25 - use Hooks::run
*/
function wfRunHooks( $event, array $args = array(), $deprecatedVersion = null ) {
return Hooks::run( $event, $args, $deprecatedVersion );
@@ -4047,7 +4021,6 @@ function wfUnpack( $format, $data, $length = false ) {
*/
function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
static $badImageCache = null; // based on bad_image_list msg
- wfProfileIn( __METHOD__ );
# Handle redirects
$redirectTitle = RepoGroup::singleton()->checkRedirect( Title::makeTitle( NS_FILE, $name ) );
@@ -4057,8 +4030,7 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
# Run the extension hook
$bad = false;
- if ( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) {
- wfProfileOut( __METHOD__ );
+ if ( !Hooks::run( 'BadImage', array( $name, &$bad ) ) ) {
return $bad;
}
@@ -4108,7 +4080,6 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
$contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false;
$bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] );
- wfProfileOut( __METHOD__ );
return $bad;
}
@@ -4121,11 +4092,23 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
*/
function wfCanIPUseHTTPS( $ip ) {
$canDo = true;
- wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) );
+ Hooks::run( 'CanIPUseHTTPS', array( $ip, &$canDo ) );
return !!$canDo;
}
/**
+ * Determine input string is represents as infinity
+ *
+ * @param string $str The string to determine
+ * @return bool
+ * @since 1.25
+ */
+function wfIsInfinity( $str ) {
+ $infinityValues = array( 'infinite', 'indefinite', 'infinity', 'never' );
+ return in_array( $str, $infinityValues );
+}
+
+/**
* Work out the IP address based on various globals
* For trusted proxies, use the XFF client IP (first of the chain)
*
@@ -4148,6 +4131,7 @@ function wfGetIP() {
* @return bool
*/
function wfIsTrustedProxy( $ip ) {
+ wfDeprecated( __METHOD__, '1.24' );
return IP::isTrustedProxy( $ip );
}
@@ -4160,5 +4144,94 @@ function wfIsTrustedProxy( $ip ) {
* @since 1.23 Supports CIDR ranges in $wgSquidServersNoPurge
*/
function wfIsConfiguredProxy( $ip ) {
+ wfDeprecated( __METHOD__, '1.24' );
return IP::isConfiguredProxy( $ip );
}
+
+/**
+ * Returns true if these thumbnail parameters match one that MediaWiki
+ * requests from file description pages and/or parser output.
+ *
+ * $params is considered non-standard if they involve a non-standard
+ * width or any non-default parameters aside from width and page number.
+ * The number of possible files with standard parameters is far less than
+ * that of all combinations; rate-limiting for them can thus be more generious.
+ *
+ * @param File $file
+ * @param array $params
+ * @return bool
+ * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25
+ */
+function wfThumbIsStandard( File $file, array $params ) {
+ global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages;
+
+ $multipliers = array( 1 );
+ if ( $wgResponsiveImages ) {
+ // These available sizes are hardcoded currently elsewhere in MediaWiki.
+ // @see Linker::processResponsiveImages
+ $multipliers[] = 1.5;
+ $multipliers[] = 2;
+ }
+
+ $handler = $file->getHandler();
+ if ( !$handler || !isset( $params['width'] ) ) {
+ return false;
+ }
+
+ $basicParams = array();
+ if ( isset( $params['page'] ) ) {
+ $basicParams['page'] = $params['page'];
+ }
+
+ $thumbLimits = array();
+ $imageLimits = array();
+ // Expand limits to account for multipliers
+ foreach ( $multipliers as $multiplier ) {
+ $thumbLimits = array_merge( $thumbLimits, array_map(
+ function ( $width ) use ( $multiplier ) {
+ return round( $width * $multiplier );
+ }, $wgThumbLimits )
+ );
+ $imageLimits = array_merge( $imageLimits, array_map(
+ function ( $pair ) use ( $multiplier ) {
+ return array(
+ round( $pair[0] * $multiplier ),
+ round( $pair[1] * $multiplier ),
+ );
+ }, $wgImageLimits )
+ );
+ }
+
+ // Check if the width matches one of $wgThumbLimits
+ if ( in_array( $params['width'], $thumbLimits ) ) {
+ $normalParams = $basicParams + array( 'width' => $params['width'] );
+ // Append any default values to the map (e.g. "lossy", "lossless", ...)
+ $handler->normaliseParams( $file, $normalParams );
+ } else {
+ // If not, then check if the width matchs one of $wgImageLimits
+ $match = false;
+ foreach ( $imageLimits as $pair ) {
+ $normalParams = $basicParams + array( 'width' => $pair[0], 'height' => $pair[1] );
+ // Decide whether the thumbnail should be scaled on width or height.
+ // Also append any default values to the map (e.g. "lossy", "lossless", ...)
+ $handler->normaliseParams( $file, $normalParams );
+ // Check if this standard thumbnail size maps to the given width
+ if ( $normalParams['width'] == $params['width'] ) {
+ $match = true;
+ break;
+ }
+ }
+ if ( !$match ) {
+ return false; // not standard for description pages
+ }
+ }
+
+ // Check that the given values for non-page, non-width, params are just defaults
+ foreach ( $params as $key => $value ) {
+ if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 29287483..dffc7bcf 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -127,14 +127,17 @@ class Hooks {
* @param string|null $deprecatedVersion Optionally, mark hook as deprecated with version number
* @return bool True if no handler aborted the hook
*
+ * @throws Exception
+ * @throws FatalError
+ * @throws MWException
* @since 1.22 A hook function is not required to return a value for
* processing to continue. Not returning a value (or explicitly
* returning null) is equivalent to returning true.
- * @throws MWException
- * @throws FatalError
*/
public static function run( $event, array $args = array(), $deprecatedVersion = null ) {
- wfProfileIn( 'hook: ' . $event );
+ $profiler = Profiler::instance();
+ $eventPS = $profiler->scopedProfileIn( 'hook: ' . $event );
+
foreach ( self::getHandlers( $event ) as $hook ) {
// Turn non-array values into an array. (Can't use casting because of objects.)
if ( !is_array( $hook ) ) {
@@ -179,7 +182,7 @@ class Hooks {
// Run autoloader (workaround for call_user_func_array bug)
// and throw error if not callable.
if ( !is_callable( $callback ) ) {
- throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" );
+ throw new MWException( 'Invalid callback ' . $func . ' in hooks for ' . $event . "\n" );
}
/*
@@ -193,8 +196,8 @@ class Hooks {
$badhookmsg = null;
$hook_args = array_merge( $hook, $args );
- // Profile first in case the Profiler causes errors.
- wfProfileIn( $func );
+ // Profile first in case the Profiler causes errors
+ $funcPS = $profiler->scopedProfileIn( $func );
set_error_handler( 'Hooks::hookErrorHandler' );
// mark hook as deprecated, if deprecation version is specified
@@ -210,8 +213,9 @@ class Hooks {
restore_error_handler();
throw $e;
}
+
restore_error_handler();
- wfProfileOut( $func );
+ $profiler->scopedProfileOut( $funcPS );
// Process the return value.
if ( is_string( $retval ) ) {
@@ -224,13 +228,11 @@ class Hooks {
"Hook $func has invalid call signature; " . $badhookmsg
);
} elseif ( $retval === false ) {
- wfProfileOut( 'hook: ' . $event );
// False was returned. Stop processing, but no error.
return false;
}
}
- wfProfileOut( 'hook: ' . $event );
return true;
}
diff --git a/includes/Html.php b/includes/Html.php
index 2e148140..d312e0a6 100644
--- a/includes/Html.php
+++ b/includes/Html.php
@@ -102,6 +102,35 @@ class Html {
);
/**
+ * Modifies a set of attributes meant for button elements
+ * and apply a set of default attributes when $wgUseMediaWikiUIEverywhere enabled.
+ * @param array $attrs
+ * @param string[] $modifiers to add to the button
+ * @see https://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers
+ * @return array $attrs A modified attribute array
+ */
+ public static function buttonAttributes( $attrs, $modifiers = array() ) {
+ global $wgUseMediaWikiUIEverywhere;
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ if ( isset( $attrs['class'] ) ) {
+ if ( is_array( $attrs['class'] ) ) {
+ $attrs['class'][] = 'mw-ui-button';
+ $attrs = array_merge( $attrs, $modifiers );
+ // ensure compatibility with Xml
+ $attrs['class'] = implode( ' ', $attrs['class'] );
+ } else {
+ $attrs['class'] .= ' mw-ui-button ' . implode( ' ', $modifiers );
+ }
+ } else {
+ $attrs['class'] = array( 'mw-ui-button' );
+ // ensure compatibility with Xml
+ $attrs['class'] = implode( ' ', array_merge( $attrs['class'], $modifiers ) );
+ }
+ }
+ return $attrs;
+ }
+
+ /**
* Modifies a set of attributes meant for text input elements
* and apply a set of default attributes.
* Removes size attribute when $wgUseMediaWikiUIEverywhere enabled.
@@ -113,28 +142,63 @@ class Html {
if ( !$attrs ) {
$attrs = array();
}
- if ( isset( $attrs['class'] ) ) {
- if ( is_array( $attrs['class'] ) ) {
- $attrs['class'][] = 'mw-ui-input';
+ if ( $wgUseMediaWikiUIEverywhere ) {
+ 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';
+ $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 link element in a string styled as a button
+ * (when $wgUseMediaWikiUIEverywhere is enabled).
+ *
+ * @param string $contents The raw HTML contents of the element: *not*
+ * escaped!
+ * @param array $attrs Associative array of attributes, e.g., array(
+ * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
+ * further documentation.
+ * @param string[] $modifiers to add to the button
+ * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers
+ * @return string Raw HTML
+ */
+ public static function linkButton( $contents, $attrs, $modifiers = array() ) {
+ return self::element( 'a',
+ self::buttonAttributes( $attrs, $modifiers ),
+ $contents
+ );
+ }
+
+ /**
+ * Returns an HTML link element in a string styled as a button
+ * (when $wgUseMediaWikiUIEverywhere is enabled).
+ *
+ * @param string $contents The raw HTML contents of the element: *not*
+ * escaped!
+ * @param array $attrs Associative array of attributes, e.g., array(
+ * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
+ * further documentation.
+ * @param string[] $modifiers to add to the button
+ * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers
+ * @return string Raw HTML
+ */
+ public static function submitButton( $contents, $attrs, $modifiers = array() ) {
+ $attrs['type'] = 'submit';
+ $attrs['value'] = $contents;
+ return self::element( 'input', self::buttonAttributes( $attrs, $modifiers ) );
+ }
+
+ /**
* 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
- * should probably just type out the html element yourself.
+ * values.
*
* This is quite similar to Xml::tags(), but it implements some useful
* HTML-specific logic. For instance, there is no $allowShortTag
@@ -193,20 +257,11 @@ class Html {
* @return string
*/
public static function openElement( $element, $attribs = array() ) {
- global $wgWellFormedXml;
$attribs = (array)$attribs;
// This is not required in HTML5, but let's do it anyway, for
// consistency and better compression.
$element = strtolower( $element );
- // In text/html, initial <html> and <head> tags can be omitted under
- // pretty much any sane circumstances, if they have no attributes. See:
- // <http://www.whatwg.org/html/syntax.html#optional-tags>
- if ( !$wgWellFormedXml && !$attribs
- && in_array( $element, array( 'html', 'head' ) ) ) {
- return '';
- }
-
// Remove invalid input types
if ( $element == 'input' ) {
$validTypes = array(
@@ -236,8 +291,7 @@ class Html {
'tel',
'color',
);
- if ( isset( $attribs['type'] )
- && !in_array( $attribs['type'], $validTypes ) ) {
+ if ( isset( $attribs['type'] ) && !in_array( $attribs['type'], $validTypes ) ) {
unset( $attribs['type'] );
}
}
@@ -331,8 +385,9 @@ class Html {
}
// Simple checks using $attribDefaults
- if ( isset( $attribDefaults[$element][$lcattrib] ) &&
- $attribDefaults[$element][$lcattrib] == $value ) {
+ if ( isset( $attribDefaults[$element][$lcattrib] )
+ && $attribDefaults[$element][$lcattrib] == $value
+ ) {
unset( $attribs[$attrib] );
}
@@ -342,8 +397,9 @@ class Html {
}
// More subtle checks
- if ( $element === 'link' && isset( $attribs['type'] )
- && strval( $attribs['type'] ) == 'text/css' ) {
+ if ( $element === 'link'
+ && isset( $attribs['type'] ) && strval( $attribs['type'] ) == 'text/css'
+ ) {
unset( $attribs['type'] );
}
if ( $element === 'input' ) {
@@ -442,8 +498,7 @@ class Html {
// For boolean attributes, support array( 'foo' ) instead of
// requiring array( 'foo' => 'meaningless' ).
- if ( is_int( $key )
- && in_array( strtolower( $value ), self::$boolAttribs ) ) {
+ if ( is_int( $key ) && in_array( strtolower( $value ), self::$boolAttribs ) ) {
$key = $value;
}
@@ -522,14 +577,13 @@ class Html {
// marks omitted, but not all. (Although a literal " is not
// permitted, we don't check for that, since it will be escaped
// anyway.)
- #
+
// See also research done on further characters that need to be
// escaped: http://code.google.com/p/html5lib/issues/detail?id=93
$badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}"
. "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}"
. "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}";
- if ( $wgWellFormedXml || $value === ''
- || preg_match( "![$badChars]!u", $value ) ) {
+ if ( $wgWellFormedXml || $value === '' || preg_match( "![$badChars]!u", $value ) ) {
$quote = '"';
} else {
$quote = '';
@@ -654,7 +708,7 @@ class Html {
* new HTML5 input types and attributes.
*
* @param string $name Name attribute
- * @param array $value Value attribute
+ * @param string $value Value attribute
* @param string $type Type attribute
* @param array $attribs Associative array of miscellaneous extra
* attributes, passed to Html::element()
@@ -665,7 +719,7 @@ class Html {
$attribs['value'] = $value;
$attribs['name'] = $name;
if ( in_array( $type, array( 'text', 'search', 'email', 'password', 'number' ) ) ) {
- $attribs = Html::getTextInputAttributes( $attribs );
+ $attribs = self::getTextInputAttributes( $attribs );
}
return self::element( 'input', $attribs );
}
@@ -676,7 +730,7 @@ class Html {
* @param string $name Name attribute
* @param bool $checked Whether the checkbox is checked or not
* @param array $attribs Array of additional attributes
- * @return string
+ * @return string Raw HTML
*/
public static function check( $name, $checked = false, array $attribs = array() ) {
if ( isset( $attribs['value'] ) ) {
@@ -699,7 +753,7 @@ class Html {
* @param string $name Name attribute
* @param bool $checked Whether the checkbox is checked or not
* @param array $attribs Array of additional attributes
- * @return string
+ * @return string Raw HTML
*/
public static function radio( $name, $checked = false, array $attribs = array() ) {
if ( isset( $attribs['value'] ) ) {
@@ -722,7 +776,7 @@ class Html {
* @param string $label Contents of the label
* @param string $id ID of the element being labeled
* @param array $attribs Additional attributes
- * @return string
+ * @return string Raw HTML
*/
public static function label( $label, $id, array $attribs = array() ) {
$attribs += array(
@@ -768,7 +822,7 @@ class Html {
} else {
$spacedValue = $value;
}
- return self::element( 'textarea', Html::getTextInputAttributes( $attribs ), $spacedValue );
+ return self::element( 'textarea', self::getTextInputAttributes( $attribs ), $spacedValue );
}
/**
@@ -833,13 +887,13 @@ class Html {
continue;
}
if ( $nsId === NS_MAIN ) {
- // For other namespaces use use the namespace prefix as label, but for
+ // For other namespaces use the namespace prefix as label, but for
// main we don't use "" but the user message describing it (e.g. "(Main)" or "(Article)")
$nsName = wfMessage( 'blanknamespace' )->text();
} elseif ( is_int( $nsId ) ) {
$nsName = $wgContLang->convertNamespace( $nsId );
}
- $optionsHtml[] = Html::element(
+ $optionsHtml[] = self::element(
'option', array(
'disabled' => in_array( $nsId, $params['disable'] ),
'value' => $nsId,
@@ -858,7 +912,7 @@ class Html {
$ret = '';
if ( isset( $params['label'] ) ) {
- $ret .= Html::element(
+ $ret .= self::element(
'label', array(
'for' => isset( $selectAttribs['id'] ) ? $selectAttribs['id'] : null,
), $params['label']
@@ -866,11 +920,11 @@ class Html {
}
// Wrap options in a <select>
- $ret .= Html::openElement( 'select', $selectAttribs )
+ $ret .= self::openElement( 'select', $selectAttribs )
. "\n"
. implode( "\n", $optionsHtml )
. "\n"
- . Html::closeElement( 'select' );
+ . self::closeElement( 'select' );
return $ret;
}
@@ -911,7 +965,7 @@ class Html {
$attribs['version'] = $wgHtml5Version;
}
- $html = Html::openElement( 'html', $attribs );
+ $html = self::openElement( 'html', $attribs );
if ( $html ) {
$html .= "\n";
@@ -946,44 +1000,58 @@ class Html {
*
* @return string
*/
- static function infoBox( $text, $icon, $alt, $class = false ) {
- $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) );
+ static function infoBox( $text, $icon, $alt, $class = '' ) {
+ $s = self::openElement( 'div', array( 'class' => "mw-infobox $class" ) );
- $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) .
- Html::element( 'img',
+ $s .= self::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) .
+ self::element( 'img',
array(
'src' => $icon,
'alt' => $alt,
)
) .
- Html::closeElement( 'div' );
+ self::closeElement( 'div' );
- $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ) .
+ $s .= self::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ) .
$text .
- Html::closeElement( 'div' );
- $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
+ self::closeElement( 'div' );
+ $s .= self::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
- $s .= Html::closeElement( 'div' );
+ $s .= self::closeElement( 'div' );
- $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
+ $s .= self::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
return $s;
}
/**
- * Generate a srcset attribute value from an array mapping pixel densities
- * to URLs. Note that srcset supports width and height values as well, which
- * are not used here.
+ * Generate a srcset attribute value.
+ *
+ * Generates a srcset attribute value from an array mapping pixel densities
+ * to URLs. A trailing 'x' in pixel density values is optional.
+ *
+ * @note srcset width and height values are not supported.
+ *
+ * @see http://www.whatwg.org/html/embedded-content-1.html#attr-img-srcset
+ *
+ * @par Example:
+ * @code
+ * Html::srcSet( array(
+ * '1x' => 'standard.jpeg',
+ * '1.5x' => 'large.jpeg',
+ * '3x' => 'extra-large.jpeg',
+ * ) );
+ * // gives 'standard.jpeg 1x, large.jpeg 1.5x, extra-large.jpeg 2x'
+ * @endcode
*
- * @param array $urls
+ * @param string[] $urls
* @return string
*/
static function srcSet( $urls ) {
$candidates = array();
foreach ( $urls as $density => $url ) {
- // Image candidate syntax per current whatwg live spec, 2012-09-23:
- // http://www.whatwg.org/html/embedded-content-1.html#attr-img-srcset
- $candidates[] = "{$url} {$density}x";
+ // Cast density to float to strip 'x'.
+ $candidates[] = $url . ' ' . (float)$density . 'x';
}
return implode( ", ", $candidates );
}
diff --git a/includes/HtmlFormatter.php b/includes/HtmlFormatter.php
index ccbfba82..b2926d17 100644
--- a/includes/HtmlFormatter.php
+++ b/includes/HtmlFormatter.php
@@ -133,7 +133,6 @@ class HtmlFormatter {
* @return array Array of removed DOMElements
*/
public function filterContent() {
- wfProfileIn( __METHOD__ );
$removals = $this->parseItemsToRemove();
// Bail out early if nothing to do
@@ -143,7 +142,6 @@ class HtmlFormatter {
},
true
) ) {
- wfProfileOut( __METHOD__ );
return array();
}
@@ -178,7 +176,7 @@ class HtmlFormatter {
// CSS Classes
$domElemsToRemove = array();
- $xpath = new DOMXpath( $doc );
+ $xpath = new DOMXPath( $doc );
foreach ( $removals['CLASS'] as $classToRemove ) {
$elements = $xpath->query( '//*[contains(@class, "' . $classToRemove . '")]' );
@@ -202,7 +200,6 @@ class HtmlFormatter {
$removed = array_merge( $removed, $this->removeElements( $elements ) );
}
- wfProfileOut( __METHOD__ );
return $removed;
}
@@ -235,7 +232,6 @@ class HtmlFormatter {
* @return string
*/
private function fixLibXML( $html ) {
- wfProfileIn( __METHOD__ );
static $replacements;
if ( !$replacements ) {
// We don't include rules like '&#34;' => '&amp;quot;' because entities had already been
@@ -249,7 +245,6 @@ class HtmlFormatter {
}
$html = $replacements->replace( $html );
$html = mb_convert_encoding( $html, 'UTF-8', 'HTML-ENTITIES' );
- wfProfileOut( __METHOD__ );
return $html;
}
@@ -264,10 +259,8 @@ class HtmlFormatter {
* @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 );
}
@@ -283,9 +276,7 @@ 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.
@@ -294,7 +285,6 @@ class HtmlFormatter {
// XML code paths if possible and fix there.
$html = str_replace( '&#13;', '', $html );
}
- wfProfileOut( __METHOD__ . '-fixes' );
} else {
$html = $this->html;
}
@@ -302,14 +292,11 @@ class HtmlFormatter {
$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;
}
@@ -322,6 +309,7 @@ class HtmlFormatter {
* @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
+ * @throws MWException
*/
protected function parseSelector( $selector, &$type, &$rawName ) {
if ( strpos( $selector, '.' ) === 0 ) {
@@ -349,7 +337,6 @@ class HtmlFormatter {
* @return array
*/
protected function parseItemsToRemove() {
- wfProfileIn( __METHOD__ );
$removals = array(
'ID' => array(),
'TAG' => array(),
@@ -371,7 +358,6 @@ class HtmlFormatter {
$removals['TAG'][] = 'video';
}
- wfProfileOut( __METHOD__ );
return $removals;
}
}
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index 83021245..8e05f597 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -55,11 +55,11 @@ class Http {
* to avoid attacks on intranet services accessible by HTTP.
* - userAgent A user agent, if you want to override the default
* MediaWiki/$wgVersion
+ * @param string $caller The method making this request, for profiling
* @return string|bool (bool)false on failure or a string on success
*/
- public static function request( $method, $url, $options = array() ) {
+ public static function request( $method, $url, $options = array(), $caller = __METHOD__ ) {
wfDebug( "HTTP: $method: $url\n" );
- wfProfileIn( __METHOD__ . "-$method" );
$options['method'] = strtoupper( $method );
@@ -70,29 +70,39 @@ class Http {
$options['connectTimeout'] = 'default';
}
- $req = MWHttpRequest::factory( $url, $options );
+ $req = MWHttpRequest::factory( $url, $options, $caller );
$status = $req->execute();
$content = false;
if ( $status->isOK() ) {
$content = $req->getContent();
}
- wfProfileOut( __METHOD__ . "-$method" );
return $content;
}
/**
* Simple wrapper for Http::request( 'GET' )
* @see Http::request()
+ * @since 1.25 Second parameter $timeout removed. Second parameter
+ * is now $options which can be given a 'timeout'
*
* @param string $url
- * @param string $timeout
* @param array $options
+ * @param string $caller The method making this request, for profiling
* @return string
*/
- public static function get( $url, $timeout = 'default', $options = array() ) {
- $options['timeout'] = $timeout;
- return Http::request( 'GET', $url, $options );
+ public static function get( $url, $options = array(), $caller = __METHOD__ ) {
+ $args = func_get_args();
+ if ( isset( $args[1] ) && ( is_string( $args[1] ) || is_numeric( $args[1] ) ) ) {
+ // Second was used to be the timeout
+ // And third parameter used to be $options
+ wfWarn( "Second parameter should not be a timeout.", 2 );
+ $options = isset( $args[2] ) && is_array( $args[2] ) ?
+ $args[2] : array();
+ $options['timeout'] = $args[1];
+ $caller = __METHOD__;
+ }
+ return Http::request( 'GET', $url, $options, $caller );
}
/**
@@ -101,10 +111,11 @@ class Http {
*
* @param string $url
* @param array $options
+ * @param string $caller The method making this request, for profiling
* @return string
*/
- public static function post( $url, $options = array() ) {
- return Http::request( 'POST', $url, $options );
+ public static function post( $url, $options = array(), $caller = __METHOD__ ) {
+ return Http::request( 'POST', $url, $options, $caller );
}
/**
@@ -114,7 +125,7 @@ class Http {
* @return bool
*/
public static function isLocalURL( $url ) {
- global $wgCommandLineMode, $wgConf;
+ global $wgCommandLineMode, $wgLocalVirtualHosts, $wgConf;
if ( $wgCommandLineMode ) {
return false;
@@ -126,7 +137,7 @@ class Http {
$host = $matches[1];
// Split up dotwise
$domainParts = explode( '.', $host );
- // Check if this domain or any superdomain is listed in $wgConf as a local virtual host
+ // Check if this domain or any superdomain is listed as a local virtual host
$domainParts = array_reverse( $domainParts );
$domain = '';
@@ -139,7 +150,9 @@ class Http {
$domain = $domainPart . '.' . $domain;
}
- if ( $wgConf->isLocalVHost( $domain ) ) {
+ if ( in_array( $domain, $wgLocalVirtualHosts )
+ || $wgConf->isLocalVHost( $domain )
+ ) {
return true;
}
}
@@ -217,10 +230,22 @@ class MWHttpRequest {
public $status;
/**
+ * @var Profiler
+ */
+ protected $profiler;
+
+ /**
+ * @var string
+ */
+ protected $profileName;
+
+ /**
* @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())
+ * @param string $caller The method making this request, for profiling
+ * @param Profiler $profiler An instance of the profiler for profiling, or null
*/
- protected function __construct( $url, $options = array() ) {
+ protected function __construct( $url, $options = array(), $caller = __METHOD__, $profiler = null ) {
global $wgHTTPTimeout, $wgHTTPConnectTimeout;
$this->url = wfExpandUrl( $url, PROTO_HTTP );
@@ -263,6 +288,10 @@ class MWHttpRequest {
if ( $this->noProxy ) {
$this->proxy = ''; // noProxy takes precedence
}
+
+ // Profile based on what's calling us
+ $this->profiler = $profiler;
+ $this->profileName = $caller;
}
/**
@@ -278,11 +307,12 @@ class MWHttpRequest {
* Generate a new request object
* @param string $url Url to use
* @param array $options (optional) extra params to pass (see Http::request())
+ * @param string $caller The method making this request, for profiling
* @throws MWException
* @return CurlHttpRequest|PhpHttpRequest
* @see MWHttpRequest::__construct
*/
- public static function factory( $url, $options = null ) {
+ public static function factory( $url, $options = null, $caller = __METHOD__ ) {
if ( !Http::$httpEngine ) {
Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
} elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
@@ -292,7 +322,7 @@ class MWHttpRequest {
switch ( Http::$httpEngine ) {
case 'curl':
- return new CurlHttpRequest( $url, $options );
+ return new CurlHttpRequest( $url, $options, $caller, Profiler::instance() );
case 'php':
if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
throw new MWException( __METHOD__ . ': allow_url_fopen ' .
@@ -301,7 +331,7 @@ class MWHttpRequest {
'http://php.net/curl.'
);
}
- return new PhpHttpRequest( $url, $options );
+ return new PhpHttpRequest( $url, $options, $caller, Profiler::instance() );
default:
throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
}
@@ -434,7 +464,6 @@ class MWHttpRequest {
* @return Status
*/
public function execute() {
- wfProfileIn( __METHOD__ );
$this->content = "";
@@ -452,7 +481,6 @@ class MWHttpRequest {
$this->setUserAgent( Http::userAgent() );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -461,7 +489,6 @@ class MWHttpRequest {
* found in an array in the member variable headerList.
*/
protected function parseHeader() {
- wfProfileIn( __METHOD__ );
$lastname = "";
@@ -480,7 +507,6 @@ class MWHttpRequest {
$this->parseCookies();
- wfProfileOut( __METHOD__ );
}
/**
@@ -614,7 +640,6 @@ class MWHttpRequest {
* Parse the cookies in the response headers and store them in the cookie jar.
*/
protected function parseCookies() {
- wfProfileIn( __METHOD__ );
if ( !$this->cookieJar ) {
$this->cookieJar = new CookieJar;
@@ -627,7 +652,6 @@ class MWHttpRequest {
}
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -715,12 +739,10 @@ class CurlHttpRequest extends MWHttpRequest {
}
public function execute() {
- wfProfileIn( __METHOD__ );
parent::execute();
if ( !$this->status->isOK() ) {
- wfProfileOut( __METHOD__ );
return $this->status;
}
@@ -766,7 +788,6 @@ class CurlHttpRequest extends MWHttpRequest {
$curlHandle = curl_init( $this->url );
if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "Error setting curl options." );
}
@@ -781,6 +802,12 @@ class CurlHttpRequest extends MWHttpRequest {
wfRestoreWarnings();
}
+ if ( $this->profiler ) {
+ $profileSection = $this->profiler->scopedProfileIn(
+ __METHOD__ . '-' . $this->profileName
+ );
+ }
+
$curlRes = curl_exec( $curlHandle );
if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) {
$this->status->fatal( 'http-timed-out', $this->url );
@@ -792,11 +819,13 @@ class CurlHttpRequest extends MWHttpRequest {
curl_close( $curlHandle );
+ if ( $this->profiler ) {
+ $this->profiler->scopedProfileOut( $profileSection );
+ }
+
$this->parseHeader();
$this->setStatus();
- wfProfileOut( __METHOD__ );
-
return $this->status;
}
@@ -832,7 +861,6 @@ class PhpHttpRequest extends MWHttpRequest {
}
public function execute() {
- wfProfileIn( __METHOD__ );
parent::execute();
@@ -903,6 +931,11 @@ class PhpHttpRequest extends MWHttpRequest {
$result = array();
+ if ( $this->profiler ) {
+ $profileSection = $this->profiler->scopedProfileIn(
+ __METHOD__ . '-' . $this->profileName
+ );
+ }
do {
$reqCount++;
wfSuppressWarnings();
@@ -933,18 +966,19 @@ class PhpHttpRequest extends MWHttpRequest {
break;
}
} while ( true );
+ if ( $this->profiler ) {
+ $this->profiler->scopedProfileOut( $profileSection );
+ }
$this->setStatus();
if ( $fh === false ) {
$this->status->fatal( 'http-request-error' );
- wfProfileOut( __METHOD__ );
return $this->status;
}
if ( $result['timed_out'] ) {
$this->status->fatal( 'http-timed-out', $this->url );
- wfProfileOut( __METHOD__ );
return $this->status;
}
@@ -966,8 +1000,6 @@ class PhpHttpRequest extends MWHttpRequest {
}
fclose( $fh );
- wfProfileOut( __METHOD__ );
-
return $this->status;
}
}
diff --git a/includes/Import.php b/includes/Import.php
index 5319076e..d31be43b 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -32,18 +32,31 @@
*/
class WikiImporter {
private $reader = null;
+ private $foreignNamespaces = null;
private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback;
- private $mSiteInfoCallback, $mTargetNamespace, $mTargetRootPage, $mPageOutCallback;
+ private $mSiteInfoCallback, $mTargetNamespace, $mPageOutCallback;
private $mNoticeCallback, $mDebug;
private $mImportUploads, $mImageBasePath;
private $mNoUpdates = false;
+ /** @var Config */
+ private $config;
+ /** @var ImportTitleFactory */
+ private $importTitleFactory;
+ /** @var array */
+ private $countableCache = array();
/**
* Creates an ImportXMLReader drawing from the source provided
- * @param ImportStreamSource $source
+ * @param ImportSource $source
+ * @param Config $config
*/
- function __construct( ImportStreamSource $source ) {
+ function __construct( ImportSource $source, Config $config = null ) {
$this->reader = new XMLReader();
+ if ( !$config ) {
+ wfDeprecated( __METHOD__ . ' without a Config instance', '1.25' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
+ $this->config = $config;
if ( !in_array( 'uploadsource', stream_get_wrappers() ) ) {
stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' );
@@ -56,10 +69,13 @@ class WikiImporter {
}
// Default callbacks
+ $this->setPageCallback( array( $this, 'beforeImportPage' ) );
$this->setRevisionCallback( array( $this, "importRevision" ) );
$this->setUploadCallback( array( $this, 'importUpload' ) );
$this->setLogItemCallback( array( $this, 'importLogItem' ) );
$this->setPageOutCallback( array( $this, 'finishImportPage' ) );
+
+ $this->importTitleFactory = new NaiveImportTitleFactory();
}
/**
@@ -192,6 +208,15 @@ class WikiImporter {
}
/**
+ * Sets the factory object to use to convert ForeignTitle objects into local
+ * Title objects
+ * @param ImportTitleFactory $factory
+ */
+ public function setImportTitleFactory( $factory ) {
+ $this->importTitleFactory = $factory;
+ }
+
+ /**
* Set a target namespace to override the defaults
* @param null|int $namespace
* @return bool
@@ -200,9 +225,16 @@ class WikiImporter {
if ( is_null( $namespace ) ) {
// Don't override namespaces
$this->mTargetNamespace = null;
- } elseif ( $namespace >= 0 ) {
- // @todo FIXME: Check for validity
- $this->mTargetNamespace = intval( $namespace );
+ $this->setImportTitleFactory( new NaiveImportTitleFactory() );
+ return true;
+ } elseif (
+ $namespace >= 0 &&
+ MWNamespace::exists( intval( $namespace ) )
+ ) {
+ $namespace = intval( $namespace );
+ $this->mTargetNamespace = $namespace;
+ $this->setImportTitleFactory( new NamespaceImportTitleFactory( $namespace ) );
+ return true;
} else {
return false;
}
@@ -217,7 +249,7 @@ class WikiImporter {
$status = Status::newGood();
if ( is_null( $rootpage ) ) {
// No rootpage
- $this->mTargetRootPage = null;
+ $this->setImportTitleFactory( new NaiveImportTitleFactory() );
} elseif ( $rootpage !== '' ) {
$rootpage = rtrim( $rootpage, '/' ); //avoid double slashes
$title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace )
@@ -236,9 +268,9 @@ class WikiImporter {
: $wgContLang->getNsText( $title->getNamespace() );
$status->fatal( 'import-rootpage-nosubpage', $displayNSText );
} else {
- // set namespace to 'all', so the namespace check in processTitle() can passed
+ // set namespace to 'all', so the namespace check in processTitle() can pass
$this->setTargetNamespace( null );
- $this->mTargetRootPage = $title->getPrefixedDBkey();
+ $this->setImportTitleFactory( new SubpageImportTitleFactory( $title ) );
}
}
}
@@ -260,6 +292,19 @@ class WikiImporter {
}
/**
+ * Default per-page callback. Sets up some things related to site statistics
+ * @param array $titleAndForeignTitle Two-element array, with Title object at
+ * index 0 and ForeignTitle object at index 1
+ * @return bool
+ */
+ public function beforeImportPage( $titleAndForeignTitle ) {
+ $title = $titleAndForeignTitle[0];
+ $page = WikiPage::factory( $title );
+ $this->countableCache['title_' . $title->getPrefixedText()] = $page->isCountable();
+ return true;
+ }
+
+ /**
* Default per-revision callback, performs the import.
* @param WikiRevision $revision
* @return bool
@@ -312,15 +357,41 @@ class WikiImporter {
/**
* Mostly for hook use
* @param Title $title
- * @param string $origTitle
+ * @param ForeignTitle $foreignTitle
* @param int $revCount
* @param int $sRevCount
* @param array $pageInfo
* @return bool
*/
- public function finishImportPage( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) {
+ public function finishImportPage( $title, $foreignTitle, $revCount,
+ $sRevCount, $pageInfo ) {
+
+ // Update article count statistics (T42009)
+ // The normal counting logic in WikiPage->doEditUpdates() is designed for
+ // one-revision-at-a-time editing, not bulk imports. In this situation it
+ // suffers from issues of slave lag. We let WikiPage handle the total page
+ // and revision count, and we implement our own custom logic for the
+ // article (content page) count.
+ $page = WikiPage::factory( $title );
+ $page->loadPageData( 'fromdbmaster' );
+ $content = $page->getContent();
+ if ( $content === null ) {
+ wfDebug( __METHOD__ . ': Skipping article count adjustment for ' . $title .
+ ' because WikiPage::getContent() returned null' );
+ } else {
+ $editInfo = $page->prepareContentForEdit( $content );
+ $countKey = 'title_' . $title->getPrefixedText();
+ $countable = $page->isCountable( $editInfo );
+ if ( array_key_exists( $countKey, $this->countableCache ) &&
+ $countable != $this->countableCache[ $countKey ] ) {
+ DeferredUpdates::addUpdate( SiteStatsUpdate::factory( array(
+ 'articles' => ( (int)$countable - (int)$this->countableCache[ $countKey ] )
+ ) ) );
+ }
+ }
+
$args = func_get_args();
- return wfRunHooks( 'AfterImportPage', $args );
+ return Hooks::run( 'AfterImportPage', $args );
}
/**
@@ -341,6 +412,20 @@ class WikiImporter {
}
/**
+ * Notify the callback function of site info
+ * @param array $siteInfo
+ * @return bool|mixed
+ */
+ private function siteInfoCallback( $siteInfo ) {
+ if ( isset( $this->mSiteInfoCallback ) ) {
+ return call_user_func_array( $this->mSiteInfoCallback,
+ array( $siteInfo, $this ) );
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Notify the callback function when a new "<page>" is reached.
* @param Title $title
*/
@@ -353,12 +438,13 @@ class WikiImporter {
/**
* Notify the callback function when a "</page>" is closed.
* @param Title $title
- * @param Title $origTitle
+ * @param ForeignTitle $foreignTitle
* @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 ) {
+ private function pageOutCallback( $title, $foreignTitle, $revCount,
+ $sucCount, $pageInfo ) {
if ( isset( $this->mPageOutCallback ) ) {
$args = func_get_args();
call_user_func_array( $this->mPageOutCallback, $args );
@@ -396,7 +482,8 @@ 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.
+ * @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 );
@@ -416,11 +503,11 @@ class WikiImporter {
$buffer = "";
while ( $this->reader->read() ) {
switch ( $this->reader->nodeType ) {
- case XmlReader::TEXT:
- case XmlReader::SIGNIFICANT_WHITESPACE:
+ case XMLReader::TEXT:
+ case XMLReader::SIGNIFICANT_WHITESPACE:
$buffer .= $this->reader->value;
break;
- case XmlReader::END_ELEMENT:
+ case XMLReader::END_ELEMENT:
return $buffer;
}
}
@@ -452,51 +539,76 @@ class WikiImporter {
$keepReading = $this->reader->read();
$skip = false;
- while ( $keepReading ) {
- $tag = $this->reader->name;
- $type = $this->reader->nodeType;
-
- if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', array( $this ) ) ) {
- // Do nothing
- } elseif ( $tag == 'mediawiki' && $type == XmlReader::END_ELEMENT ) {
- break;
- } elseif ( $tag == 'siteinfo' ) {
- $this->handleSiteInfo();
- } elseif ( $tag == 'page' ) {
- $this->handlePage();
- } elseif ( $tag == 'logitem' ) {
- $this->handleLogItem();
- } elseif ( $tag != '#text' ) {
- $this->warn( "Unhandled top-level XML tag $tag" );
-
- $skip = true;
- }
+ $rethrow = null;
+ try {
+ while ( $keepReading ) {
+ $tag = $this->reader->name;
+ $type = $this->reader->nodeType;
+
+ if ( !Hooks::run( 'ImportHandleToplevelXMLTag', array( $this ) ) ) {
+ // Do nothing
+ } elseif ( $tag == 'mediawiki' && $type == XMLReader::END_ELEMENT ) {
+ break;
+ } elseif ( $tag == 'siteinfo' ) {
+ $this->handleSiteInfo();
+ } elseif ( $tag == 'page' ) {
+ $this->handlePage();
+ } elseif ( $tag == 'logitem' ) {
+ $this->handleLogItem();
+ } elseif ( $tag != '#text' ) {
+ $this->warn( "Unhandled top-level XML tag $tag" );
+
+ $skip = true;
+ }
- if ( $skip ) {
- $keepReading = $this->reader->next();
- $skip = false;
- $this->debug( "Skip" );
- } else {
- $keepReading = $this->reader->read();
+ if ( $skip ) {
+ $keepReading = $this->reader->next();
+ $skip = false;
+ $this->debug( "Skip" );
+ } else {
+ $keepReading = $this->reader->read();
+ }
}
+ } catch ( Exception $ex ) {
+ $rethrow = $ex;
}
+ // finally
libxml_disable_entity_loader( $oldDisable );
+ $this->reader->close();
+
+ if ( $rethrow ) {
+ throw $rethrow;
+ }
+
return true;
}
- /**
- * @return bool
- * @throws MWException
- */
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 ) {
- $this->reader->next();
- return true;
+ $this->debug( "Enter site info handler." );
+ $siteInfo = array();
+
+ // Fields that can just be stuffed in the siteInfo object
+ $normalFields = array( 'sitename', 'base', 'generator', 'case' );
+
+ while ( $this->reader->read() ) {
+ if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ $this->reader->name == 'siteinfo' ) {
+ break;
+ }
+
+ $tag = $this->reader->name;
+
+ if ( $tag == 'namespace' ) {
+ $this->foreignNamespaces[ $this->nodeAttribute( 'key' ) ] =
+ $this->nodeContents();
+ } elseif ( in_array( $tag, $normalFields ) ) {
+ $siteInfo[$tag] = $this->nodeContents();
+ }
}
- throw new MWException( "SiteInfo tag is not yet handled, do not set mSiteInfoCallback" );
+
+ $siteInfo['_namespaces'] = $this->foreignNamespaces;
+ $this->siteInfoCallback( $siteInfo );
}
private function handleLogItem() {
@@ -508,14 +620,14 @@ class WikiImporter {
'logtitle', 'params' );
while ( $this->reader->read() ) {
- if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
$this->reader->name == 'logitem' ) {
break;
}
$tag = $this->reader->name;
- if ( !wfRunHooks( 'ImportHandleLogItemXMLTag', array(
+ if ( !Hooks::run( 'ImportHandleLogItemXMLTag', array(
$this, $logInfo
) ) ) {
// Do nothing
@@ -536,7 +648,7 @@ class WikiImporter {
* @return bool|mixed
*/
private function processLogItem( $logInfo ) {
- $revision = new WikiRevision;
+ $revision = new WikiRevision( $this->config );
$revision->setID( $logInfo['id'] );
$revision->setType( $logInfo['type'] );
@@ -566,23 +678,25 @@ class WikiImporter {
$pageInfo = array( 'revisionCount' => 0, 'successfulRevisionCount' => 0 );
// Fields that can just be stuffed in the pageInfo object
- $normalFields = array( 'title', 'id', 'redirect', 'restrictions' );
+ $normalFields = array( 'title', 'ns', 'id', 'redirect', 'restrictions' );
$skip = false;
$badTitle = false;
while ( $skip ? $this->reader->next() : $this->reader->read() ) {
- if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
$this->reader->name == 'page' ) {
break;
}
+ $skip = false;
+
$tag = $this->reader->name;
if ( $badTitle ) {
// The title is invalid, bail out of this page
$skip = true;
- } elseif ( !wfRunHooks( 'ImportHandlePageXMLTag', array( $this,
+ } elseif ( !Hooks::run( 'ImportHandlePageXMLTag', array( $this,
&$pageInfo ) ) ) {
// Do nothing
} elseif ( in_array( $tag, $normalFields ) ) {
@@ -597,29 +711,35 @@ class WikiImporter {
$pageInfo[$tag] = $this->nodeAttribute( 'title' );
} else {
$pageInfo[$tag] = $this->nodeContents();
- if ( $tag == 'title' ) {
- $title = $this->processTitle( $pageInfo['title'] );
+ }
+ } elseif ( $tag == 'revision' || $tag == 'upload' ) {
+ if ( !isset( $title ) ) {
+ $title = $this->processTitle( $pageInfo['title'],
+ isset( $pageInfo['ns'] ) ? $pageInfo['ns'] : null );
+
+ if ( !$title ) {
+ $badTitle = true;
+ $skip = true;
+ }
- if ( !$title ) {
- $badTitle = true;
- $skip = true;
- }
+ $this->pageCallback( $title );
+ list( $pageInfo['_title'], $foreignTitle ) = $title;
+ }
- $this->pageCallback( $title );
- list( $pageInfo['_title'], $origTitle ) = $title;
+ if ( $title ) {
+ if ( $tag == 'revision' ) {
+ $this->handleRevision( $pageInfo );
+ } else {
+ $this->handleUpload( $pageInfo );
}
}
- } elseif ( $tag == 'revision' ) {
- $this->handleRevision( $pageInfo );
- } elseif ( $tag == 'upload' ) {
- $this->handleUpload( $pageInfo );
} elseif ( $tag != '#text' ) {
$this->warn( "Unhandled page XML tag $tag" );
$skip = true;
}
}
- $this->pageOutCallback( $pageInfo['_title'], $origTitle,
+ $this->pageOutCallback( $pageInfo['_title'], $foreignTitle,
$pageInfo['revisionCount'],
$pageInfo['successfulRevisionCount'],
$pageInfo );
@@ -637,14 +757,14 @@ class WikiImporter {
$skip = false;
while ( $skip ? $this->reader->next() : $this->reader->read() ) {
- if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
$this->reader->name == 'revision' ) {
break;
}
$tag = $this->reader->name;
- if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', array(
+ if ( !Hooks::run( 'ImportHandleRevisionXMLTag', array(
$this, $pageInfo, $revisionInfo
) ) ) {
// Do nothing
@@ -670,7 +790,7 @@ class WikiImporter {
* @return bool|mixed
*/
private function processRevision( $pageInfo, $revisionInfo ) {
- $revision = new WikiRevision;
+ $revision = new WikiRevision( $this->config );
if ( isset( $revisionInfo['id'] ) ) {
$revision->setID( $revisionInfo['id'] );
@@ -729,14 +849,14 @@ class WikiImporter {
$skip = false;
while ( $skip ? $this->reader->next() : $this->reader->read() ) {
- if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
$this->reader->name == 'upload' ) {
break;
}
$tag = $this->reader->name;
- if ( !wfRunHooks( 'ImportHandleUploadXMLTag', array(
+ if ( !Hooks::run( 'ImportHandleUploadXMLTag', array(
$this, $pageInfo
) ) ) {
// Do nothing
@@ -786,7 +906,7 @@ class WikiImporter {
* @return mixed
*/
private function processUpload( $pageInfo, $uploadInfo ) {
- $revision = new WikiRevision;
+ $revision = new WikiRevision( $this->config );
$text = isset( $uploadInfo['text'] ) ? $uploadInfo['text'] : '';
$revision->setTitle( $pageInfo['_title'] );
@@ -827,7 +947,7 @@ class WikiImporter {
$info = array();
while ( $this->reader->read() ) {
- if ( $this->reader->nodeType == XmlReader::END_ELEMENT &&
+ if ( $this->reader->nodeType == XMLReader::END_ELEMENT &&
$this->reader->name == 'contributor' ) {
break;
}
@@ -844,29 +964,27 @@ class WikiImporter {
/**
* @param string $text
+ * @param string|null $ns
* @return array|bool
*/
- private function processTitle( $text ) {
- global $wgCommandLineMode;
-
- $workTitle = $text;
- $origTitle = Title::newFromText( $workTitle );
-
- if ( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) {
- # makeTitleSafe, because $origTitle can have a interwiki (different setting of interwiki map)
- # and than dbKey can begin with a lowercase char
- $title = Title::makeTitleSafe( $this->mTargetNamespace,
- $origTitle->getDBkey() );
+ private function processTitle( $text, $ns = null ) {
+ if ( is_null( $this->foreignNamespaces ) ) {
+ $foreignTitleFactory = new NaiveForeignTitleFactory();
} else {
- if ( !is_null( $this->mTargetRootPage ) ) {
- $workTitle = $this->mTargetRootPage . '/' . $workTitle;
- }
- $title = Title::newFromText( $workTitle );
+ $foreignTitleFactory = new NamespaceAwareForeignTitleFactory(
+ $this->foreignNamespaces );
}
+ $foreignTitle = $foreignTitleFactory->createForeignTitle( $text,
+ intval( $ns ) );
+
+ $title = $this->importTitleFactory->createTitleFromForeignTitle(
+ $foreignTitle );
+
+ $commandLineMode = $this->config->get( 'CommandLineMode' );
if ( is_null( $title ) ) {
# Invalid page title? Ignore the page
- $this->notice( 'import-error-invalid', $workTitle );
+ $this->notice( 'import-error-invalid', $foreignTitle->getFullText() );
return false;
} elseif ( $title->isExternal() ) {
$this->notice( 'import-error-interwiki', $title->getPrefixedText() );
@@ -874,17 +992,17 @@ class WikiImporter {
} elseif ( !$title->canExist() ) {
$this->notice( 'import-error-special', $title->getPrefixedText() );
return false;
- } elseif ( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) {
+ } elseif ( !$title->userCan( 'edit' ) && !$commandLineMode ) {
# Do not import if the importing wiki user cannot edit this page
$this->notice( 'import-error-edit', $title->getPrefixedText() );
return false;
- } elseif ( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) {
+ } elseif ( !$title->exists() && !$title->userCan( 'create' ) && !$commandLineMode ) {
# Do not import if the importing wiki user cannot create this page
$this->notice( 'import-error-create', $title->getPrefixedText() );
return false;
}
- return array( $title, $origTitle );
+ return array( $title, $foreignTitle );
}
}
@@ -903,10 +1021,10 @@ class UploadSourceAdapter {
private $mPosition;
/**
- * @param ImportStreamSource $source
+ * @param ImportSource $source
* @return string
*/
- static function registerSource( ImportStreamSource $source ) {
+ static function registerSource( ImportSource $source ) {
$id = wfRandomString();
self::$sourceRegistrations[$id] = $source;
@@ -1093,6 +1211,13 @@ class WikiRevision {
/** @var bool */
private $mNoUpdates = false;
+ /** @var Config $config */
+ private $config;
+
+ public function __construct( Config $config ) {
+ $this->config = $config;
+ }
+
/**
* @param Title $title
* @throws MWException
@@ -1434,8 +1559,7 @@ class WikiRevision {
}
// avoid memory leak...?
- $linkCache = LinkCache::singleton();
- $linkCache->clear();
+ Title::clearCaches();
$page = WikiPage::factory( $this->title );
$page->loadPageData( 'fromdbmaster' );
@@ -1461,7 +1585,6 @@ class WikiRevision {
$this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" );
return false;
}
- $oldcountable = $page->isCountable();
}
# @todo FIXME: Use original rev_id optionally (better for backups)
@@ -1484,10 +1607,11 @@ class WikiRevision {
if ( $changed !== false && !$this->mNoUpdates ) {
wfDebug( __METHOD__ . ": running updates\n" );
+ // countable/oldcountable stuff is handled in WikiImporter::finishImportPage
$page->doEditUpdates(
$revision,
$userObj,
- array( 'created' => $created, 'oldcountable' => $oldcountable )
+ array( 'created' => $created, 'oldcountable' => 'no-change' )
);
}
@@ -1550,6 +1674,7 @@ class WikiRevision {
RepoGroup::singleton()->getLocalRepo(), $archiveName );
} else {
$file = wfLocalFile( $this->getTitle() );
+ $file->load( File::READ_LATEST );
wfDebug( __METHOD__ . 'Importing new file as ' . $file->getName() . "\n" );
if ( $file->exists() && $file->getTimestamp() > $this->getTimestamp() ) {
$archiveName = $file->getTimestamp() . '!' . $file->getName();
@@ -1599,7 +1724,7 @@ class WikiRevision {
wfDebug( __METHOD__ . ": Successful\n" );
return true;
} else {
- wfDebug( __METHOD__ . ': failed: ' . $status->getXml() . "\n" );
+ wfDebug( __METHOD__ . ': failed: ' . $status->getHTML() . "\n" );
return false;
}
}
@@ -1608,8 +1733,7 @@ class WikiRevision {
* @return bool|string
*/
function downloadSource() {
- global $wgEnableUploads;
- if ( !$wgEnableUploads ) {
+ if ( !$this->config->get( 'EnableUploads' ) ) {
return false;
}
@@ -1622,7 +1746,7 @@ class WikiRevision {
// @todo FIXME!
$src = $this->getSrc();
- $data = Http::get( $src );
+ $data = Http::get( $src, array(), __METHOD__ );
if ( !$data ) {
wfDebug( "IMPORT: couldn't fetch source $src\n" );
fclose( $f );
@@ -1639,10 +1763,37 @@ class WikiRevision {
}
/**
- * @todo document (e.g. one-sentence class description).
+ * Source interface for XML import.
+ */
+interface ImportSource {
+
+ /**
+ * Indicates whether the end of the input has been reached.
+ * Will return true after a finite number of calls to readChunk.
+ *
+ * @return bool true if there is no more input, false otherwise.
+ */
+ function atEnd();
+
+ /**
+ * Return a chunk of the input, as a (possibly empty) string.
+ * When the end of input is reached, readChunk() returns false.
+ * If atEnd() returns false, readChunk() will return a string.
+ * If atEnd() returns true, readChunk() will return false.
+ *
+ * @return bool|string
+ */
+ function readChunk();
+}
+
+/**
+ * Used for importing XML dumps where the content of the dump is in a string.
+ * This class is ineffecient, and should only be used for small dumps.
+ * For larger dumps, ImportStreamSource should be used instead.
+ *
* @ingroup SpecialPage
*/
-class ImportStringSource {
+class ImportStringSource implements ImportSource {
function __construct( $string ) {
$this->mString = $string;
$this->mRead = false;
@@ -1668,10 +1819,10 @@ class ImportStringSource {
}
/**
- * @todo document (e.g. one-sentence class description).
+ * Imports a XML dump from a file (either from file upload, files on disk, or HTTP)
* @ingroup SpecialPage
*/
-class ImportStreamSource {
+class ImportStreamSource implements ImportSource {
function __construct( $handle ) {
$this->mHandle = $handle;
}
@@ -1752,7 +1903,7 @@ class ImportStreamSource {
# quicker and sorts out user-agent problems which might
# otherwise prevent importing from large sites, such
# as the Wikimedia cluster, etc.
- $data = Http::request( $method, $url, array( 'followRedirects' => true ) );
+ $data = Http::request( $method, $url, array( 'followRedirects' => true ), __METHOD__ );
if ( $data !== false ) {
$file = tmpfile();
fwrite( $file, $data );
diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php
index 340ae8f3..99aaaa09 100644
--- a/includes/LinkFilter.php
+++ b/includes/LinkFilter.php
@@ -92,7 +92,7 @@ class LinkFilter {
* @return array Array to be passed to DatabaseBase::buildLike() or false on error
*/
public static function makeLikeArray( $filterEntry, $protocol = 'http://' ) {
- $db = wfGetDB( DB_MASTER );
+ $db = wfGetDB( DB_SLAVE );
$target = $protocol . $filterEntry;
$bits = wfParseUrl( $target );
diff --git a/includes/Linker.php b/includes/Linker.php
index be850d02..b58dabab 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -37,23 +37,10 @@ class Linker {
const TOOL_LINKS_EMAIL = 2;
/**
- * 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
- * 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.
- */
- static function getExternalLinkAttributes( $class = 'external' ) {
- wfDeprecated( __METHOD__, '1.18' );
- return self::getLinkAttributesInternal( '', $class );
- }
-
- /**
* Get the appropriate HTML attributes to add to the "a" element of an interwiki link.
*
+ * @deprecated since 1.25
+ *
* @param string $title The title text for the link, URL-encoded (???) but
* not HTML-escaped
* @param string $unused Unused
@@ -64,6 +51,8 @@ class Linker {
static function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) {
global $wgContLang;
+ wfDeprecated( __METHOD__, '1.25' );
+
# @todo FIXME: We have a whole bunch of handling here that doesn't happen in
# getExternalLinkAttributes, why?
$title = urldecode( $title );
@@ -76,6 +65,8 @@ class Linker {
/**
* Get the appropriate HTML attributes to add to the "a" element of an internal link.
*
+ * @deprecated since 1.25
+ *
* @param string $title The title text for the link, URL-encoded (???) but
* not HTML-escaped
* @param string $unused Unused
@@ -83,6 +74,8 @@ class Linker {
* @return string
*/
static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
+ wfDeprecated( __METHOD__, '1.25' );
+
$title = urldecode( $title );
$title = str_replace( '_', ' ', $title );
return self::getLinkAttributesInternal( $title, $class );
@@ -92,6 +85,8 @@ 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.
*
+ * @deprecated since 1.25
+ *
* @param Title $nt
* @param string $unused Unused
* @param string $class The contents of the class attribute, default none
@@ -100,6 +95,8 @@ class Linker {
* @return string
*/
static function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
+ wfDeprecated( __METHOD__, '1.25' );
+
if ( $title === false ) {
$title = $nt->getPrefixedText();
}
@@ -109,12 +106,16 @@ class Linker {
/**
* Common code for getLinkAttributesX functions
*
+ * @deprecated since 1.25
+ *
* @param string $title
* @param string $class
*
* @return string
*/
private static function getLinkAttributesInternal( $title, $class ) {
+ wfDeprecated( __METHOD__, '1.25' );
+
$title = htmlspecialchars( $title );
$class = htmlspecialchars( $class );
$r = '';
@@ -193,10 +194,9 @@ class Linker {
$target, $html = null, $customAttribs = array(), $query = array(), $options = array()
) {
if ( !$target instanceof Title ) {
- wfWarn( __METHOD__ . ': Requires $target to be a Title object.' );
+ wfWarn( __METHOD__ . ': Requires $target to be a Title object.', 2 );
return "<!-- ERROR -->$html";
}
- wfProfileIn( __METHOD__ );
if ( is_string( $query ) ) {
// some functions withing core using this still hand over query strings
@@ -208,9 +208,9 @@ class Linker {
$dummy = new DummyLinker; // dummy linker instance for bc on the hooks
$ret = null;
- if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html,
- &$customAttribs, &$query, &$options, &$ret ) ) ) {
- wfProfileOut( __METHOD__ );
+ if ( !Hooks::run( 'LinkBegin',
+ array( $dummy, $target, &$html, &$customAttribs, &$query, &$options, &$ret ) )
+ ) {
return $ret;
}
@@ -218,15 +218,13 @@ class Linker {
$target = self::normaliseSpecialPage( $target );
# If we don't know whether the page exists, let's find out.
- wfProfileIn( __METHOD__ . '-checkPageExistence' );
- if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
+ if ( !in_array( 'known', $options ) && !in_array( 'broken', $options ) ) {
if ( $target->isKnown() ) {
$options[] = 'known';
} else {
$options[] = 'broken';
}
}
- wfProfileOut( __METHOD__ . '-checkPageExistence' );
$oldquery = array();
if ( in_array( "forcearticlepath", $options ) && $query ) {
@@ -249,11 +247,10 @@ class Linker {
}
$ret = null;
- if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) {
+ if ( Hooks::run( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) {
$ret = Html::rawElement( 'a', $attribs, $html );
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -278,7 +275,6 @@ class Linker {
* @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->hasFragment() ) {
@@ -304,7 +300,6 @@ class Linker {
}
$ret = $target->getLinkURL( $query, false, $proto );
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -318,12 +313,10 @@ class Linker {
* @return array
*/
private static function linkAttribs( $target, $attribs, $options ) {
- wfProfileIn( __METHOD__ );
global $wgUser;
$defaults = array();
if ( !in_array( 'noclasses', $options ) ) {
- wfProfileIn( __METHOD__ . '-getClasses' );
# Now build the classes.
$classes = array();
@@ -344,7 +337,6 @@ class Linker {
if ( $classes != array() ) {
$defaults['class'] = implode( ' ', $classes );
}
- wfProfileOut( __METHOD__ . '-getClasses' );
}
# Get a default title attribute.
@@ -364,11 +356,10 @@ class Linker {
foreach ( $merged as $key => $val ) {
# A false value suppresses the attribute, and we don't want the
# href attribute to be overridden.
- if ( $key != 'href' and $val !== false ) {
+ if ( $key != 'href' && $val !== false ) {
$ret[$key] = $val;
}
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -409,7 +400,7 @@ class Linker {
*/
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 ) ) ) {
+ if ( !Hooks::run( 'SelfLinkBegin', array( $nt, &$html, &$trail, &$prefix, &$ret ) ) ) {
return $ret;
}
@@ -495,7 +486,7 @@ class Linker {
$alt = self::fnamePart( $url );
}
$img = '';
- $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
+ $success = Hooks::run( '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 );
@@ -549,7 +540,7 @@ class Linker {
) {
$res = null;
$dummy = new DummyLinker;
- if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
+ if ( !Hooks::run( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
&$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
return $res;
}
@@ -931,7 +922,6 @@ class Linker {
}
global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
- wfProfileIn( __METHOD__ );
if ( $label == '' ) {
$label = $title->getPrefixedText();
}
@@ -944,19 +934,16 @@ class Linker {
$redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
if ( $redir ) {
- wfProfileOut( __METHOD__ );
return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
}
$href = self::getUploadUrl( $title, $query );
- wfProfileOut( __METHOD__ );
return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
$encLabel . '</a>';
}
- wfProfileOut( __METHOD__ );
return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
}
@@ -1029,7 +1016,7 @@ class Linker {
'title' => $alt
);
- if ( !wfRunHooks( 'LinkerMakeMediaLinkFile',
+ if ( !Hooks::run( '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 );
@@ -1088,7 +1075,7 @@ class Linker {
}
$attribs['rel'] = Parser::getExternalLinkRel( $url, $title );
$link = '';
- $success = wfRunHooks( 'LinkerMakeExternalLink',
+ $success = Hooks::run( 'LinkerMakeExternalLink',
array( &$url, &$text, &$link, &$attribs, $linktype ) );
if ( !$success ) {
wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
@@ -1174,10 +1161,10 @@ class Linker {
$items[] = self::emailLink( $userId, $userText );
}
- wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
+ Hooks::run( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
if ( $items ) {
- return wfMessage( 'word-separator' )->plain()
+ return wfMessage( 'word-separator' )->escaped()
. '<span class="mw-usertoollinks">'
. wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
. '</span>';
@@ -1264,7 +1251,6 @@ class Linker {
$userId = $rev->getUser( Revision::FOR_THIS_USER );
$userText = $rev->getUserText( Revision::FOR_THIS_USER );
$link = self::userLink( $userId, $userText )
- . wfMessage( 'word-separator' )->plain()
. self::userToolLinks( $userId, $userText );
} else {
$link = wfMessage( 'rev-deleted-user' )->escaped();
@@ -1293,7 +1279,6 @@ class Linker {
* @return mixed|string
*/
public static function formatComment( $comment, $title = null, $local = false ) {
- wfProfileIn( __METHOD__ );
# Sanitize text a bit:
$comment = str_replace( "\n", " ", $comment );
@@ -1304,12 +1289,12 @@ class Linker {
$comment = self::formatAutocomments( $comment, $title, $local );
$comment = self::formatLinksInComment( $comment, $title, $local );
- wfProfileOut( __METHOD__ );
return $comment;
}
/**
* Converts autogenerated comments in edit summaries into section links.
+ *
* The pattern for autogen comments is / * foo * /, which makes for
* some nasty regex.
* We look for all comments, match any text before and after the comment,
@@ -1322,16 +1307,30 @@ class Linker {
* @return string Formatted comment
*/
private static function formatAutocomments( $comment, $title = null, $local = false ) {
- return preg_replace_callback(
- '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
- function ( $match ) use ( $title, $local ) {
+ // @todo $append here is something of a hack to preserve the status
+ // quo. Someone who knows more about bidi and such should decide
+ // (1) what sane rendering even *is* for an LTR edit summary on an RTL
+ // wiki, both when autocomments exist and when they don't, and
+ // (2) what markup will make that actually happen.
+ $append = '';
+ $comment = preg_replace_callback(
+ // To detect the presence of content before or after the
+ // auto-comment, we use capturing groups inside optional zero-width
+ // assertions. But older versions of PCRE can't directly make
+ // zero-width assertions optional, so wrap them in a non-capturing
+ // group.
+ '!(?:(?<=(.)))?/\*\s*(.*?)\s*\*/(?:(?=(.)))?!',
+ function ( $match ) use ( $title, $local, &$append ) {
global $wgLang;
- $pre = $match[1];
+ // Ensure all match positions are defined
+ $match += array( '', '', '', '' );
+
+ $pre = $match[1] !== '';
$auto = $match[2];
- $post = $match[3];
+ $post = $match[3] !== '';
$comment = null;
- wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
+ Hooks::run( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
if ( $comment === null ) {
$link = '';
if ( $title ) {
@@ -1359,7 +1358,7 @@ class Linker {
}
if ( $pre ) {
# written summary $presep autocomment (summary /* section */)
- $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
+ $pre = wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
}
if ( $post ) {
# autocomment $postsep written summary (/* section */ summary)
@@ -1367,12 +1366,14 @@ class Linker {
}
$auto = '<span class="autocomment">' . $auto . '</span>';
$comment = $pre . $link . $wgLang->getDirMark()
- . '<span dir="auto">' . $auto . $post . '</span>';
+ . '<span dir="auto">' . $auto;
+ $append .= '</span>';
}
return $comment;
},
$comment
);
+ return $comment . $append;
}
/**
@@ -1383,9 +1384,13 @@ class Linker {
* @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
+ * @param string|null $wikiId Id of the wiki to link to (if not the local wiki), as used by WikiMap
+ *
* @return string
*/
- public static function formatLinksInComment( $comment, $title = null, $local = false ) {
+ public static function formatLinksInComment(
+ $comment, $title = null, $local = false, $wikiId = null
+ ) {
return preg_replace_callback(
'/
\[\[
@@ -1399,7 +1404,7 @@ class Linker {
\]\]
([^[]*) # 3. link trail (the text up until the next link)
/x',
- function ( $match ) use ( $title, $local ) {
+ function ( $match ) use ( $title, $local, $wikiId ) {
global $wgContLang;
$medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
@@ -1455,11 +1460,22 @@ class Linker {
$newTarget = clone ( $title );
$newTarget->setFragment( '#' . $target->getFragment() );
$target = $newTarget;
+
}
- $thelink = Linker::link(
- $target,
- $linkText . $inside
- ) . $trail;
+
+ if ( $wikiId !== null ) {
+ $thelink = Linker::makeExternalLink(
+ WikiMap::getForeignURL( $wikiId, $target->getFullText() ),
+ $linkText . $inside,
+ /* escape = */ false // Already escaped
+ ) . $trail;
+ } else {
+ $thelink = Linker::link(
+ $target,
+ $linkText . $inside
+ ) . $trail;
+ }
+
}
}
if ( $thelink ) {
@@ -1489,11 +1505,13 @@ class Linker {
# Foobar -- normal
# :Foobar -- override special treatment of prefix (images, language links)
# /Foobar -- convert to CurrentPage/Foobar
- # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
+ # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
# ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
- # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage
+ # ../Foobar -- convert to CurrentPage/Foobar,
+ # (from CurrentPage/CurrentSubPage)
+ # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
+ # (from CurrentPage/CurrentSubPage)
- wfProfileIn( __METHOD__ );
$ret = $target; # default return value is no change
# Some namespaces don't allow subpages,
@@ -1537,7 +1555,7 @@ class Linker {
$ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
# / at the end means don't show full path
if ( substr( $nodotdot, -1, 1 ) === '/' ) {
- $nodotdot = substr( $nodotdot, 0, -1 );
+ $nodotdot = rtrim( $nodotdot, '/' );
if ( $text === '' ) {
$text = $nodotdot . $suffix;
}
@@ -1552,7 +1570,6 @@ class Linker {
}
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -1589,7 +1606,7 @@ class Linker {
* @return string HTML fragment
*/
public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
- if ( $rev->getRawComment() == "" ) {
+ if ( $rev->getComment( Revision::RAW ) == "" ) {
return "";
}
if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
@@ -1807,7 +1824,7 @@ class Linker {
$inner = self::buildRollbackLink( $rev, $context, $editCount );
if ( !in_array( 'noBrackets', $options ) ) {
- $inner = $context->msg( 'brackets' )->rawParams( $inner )->plain();
+ $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
}
return '<span class="mw-rollback-link">' . $inner . '</span>';
@@ -1854,7 +1871,7 @@ class Linker {
$editCount = 0;
$moreRevs = false;
foreach ( $res as $row ) {
- if ( $rev->getRawUserText() != $row->rev_user_text ) {
+ if ( $rev->getUserText( Revision::RAW ) != $row->rev_user_text ) {
if ( $verify &&
( $row->rev_deleted & Revision::DELETED_TEXT
|| $row->rev_deleted & Revision::DELETED_USER
@@ -1892,7 +1909,7 @@ class Linker {
) {
global $wgShowRollbackEditCount, $wgMiserMode;
- // To config which pages are effected by miser mode
+ // To config which pages are affected by miser mode
$disableRollbackEditCountSpecialPage = array( 'Recentchanges', 'Watchlist' );
if ( $context === null ) {
@@ -1975,7 +1992,6 @@ class Linker {
$section = false, $more = null
) {
global $wgLang;
- wfProfileIn( __METHOD__ );
$outText = '';
if ( count( $templates ) > 0 ) {
@@ -2028,14 +2044,14 @@ class Linker {
if ( $titleObj->quickUserCan( 'edit' ) ) {
$editLink = self::link(
$titleObj,
- wfMessage( 'editlink' )->text(),
+ wfMessage( 'editlink' )->escaped(),
array(),
array( 'action' => 'edit' )
);
} else {
$editLink = self::link(
$titleObj,
- wfMessage( 'viewsourcelink' )->text(),
+ wfMessage( 'viewsourcelink' )->escaped(),
array(),
array( 'action' => 'edit' )
);
@@ -2055,7 +2071,6 @@ class Linker {
$outText .= '</ul>';
}
- wfProfileOut( __METHOD__ );
return $outText;
}
@@ -2067,7 +2082,6 @@ class Linker {
* @return string HTML output
*/
public static function formatHiddenCategories( $hiddencats ) {
- wfProfileIn( __METHOD__ );
$outText = '';
if ( count( $hiddencats ) > 0 ) {
@@ -2084,7 +2098,6 @@ class Linker {
}
$outText .= '</ul>';
}
- wfProfileOut( __METHOD__ );
return $outText;
}
@@ -2113,7 +2126,6 @@ class Linker {
* escape), or false for no title attribute
*/
public static function titleAttrib( $name, $options = null ) {
- wfProfileIn( __METHOD__ );
$message = wfMessage( "tooltip-$name" );
@@ -2142,7 +2154,6 @@ class Linker {
}
}
- wfProfileOut( __METHOD__ );
return $tooltip;
}
@@ -2162,7 +2173,6 @@ class Linker {
if ( isset( self::$accesskeycache[$name] ) ) {
return self::$accesskeycache[$name];
}
- wfProfileIn( __METHOD__ );
$message = wfMessage( "accesskey-$name" );
@@ -2178,7 +2188,6 @@ class Linker {
}
}
- wfProfileOut( __METHOD__ );
self::$accesskeycache[$name] = $accesskey;
return self::$accesskeycache[$name];
}
@@ -2286,7 +2295,6 @@ class Linker {
static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
wfDeprecated( __METHOD__, '1.21' );
- wfProfileIn( __METHOD__ );
$query = wfCgiToArray( $query );
list( $inside, $trail ) = self::splitTrail( $trail );
if ( $text === '' ) {
@@ -2295,7 +2303,6 @@ class Linker {
$ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -2320,8 +2327,6 @@ class Linker {
) {
wfDeprecated( __METHOD__, '1.21' );
- wfProfileIn( __METHOD__ );
-
if ( $text == '' ) {
$text = self::linkText( $title );
}
@@ -2335,7 +2340,6 @@ class Linker {
$ret = self::link( $title, "$prefix$text$inside", $attribs, $query,
array( 'known', 'noclasses' ) ) . $trail;
- wfProfileOut( __METHOD__ );
return $ret;
}
diff --git a/includes/MWNamespace.php b/includes/MWNamespace.php
index 392f5582..bd685514 100644
--- a/includes/MWNamespace.php
+++ b/includes/MWNamespace.php
@@ -72,7 +72,7 @@ class MWNamespace {
/**
* @since 1.20
*/
- wfRunHooks( 'NamespaceIsMovable', array( $index, &$result ) );
+ Hooks::run( 'NamespaceIsMovable', array( $index, &$result ) );
return $result;
}
@@ -213,7 +213,7 @@ class MWNamespace {
if ( is_array( $wgExtraNamespaces ) ) {
$namespaces += $wgExtraNamespaces;
}
- wfRunHooks( 'CanonicalNamespaces', array( &$namespaces ) );
+ Hooks::run( 'CanonicalNamespaces', array( &$namespaces ) );
}
return $namespaces;
}
diff --git a/includes/MWTimestamp.php b/includes/MWTimestamp.php
index 26f5e543..ea91470e 100644
--- a/includes/MWTimestamp.php
+++ b/includes/MWTimestamp.php
@@ -182,6 +182,11 @@ class MWTimestamp {
$output .= ' GMT';
}
+ if ( $style == TS_MW && strlen( $output ) !== 14 ) {
+ throw new TimestampException( __METHOD__ . ': The timestamp cannot be represented in ' .
+ 'the specified format' );
+ }
+
return $output;
}
@@ -221,7 +226,7 @@ class MWTimestamp {
$offsetRel = $relativeTo->offsetForUser( $user );
$ts = '';
- if ( wfRunHooks( 'GetHumanTimestamp', array( &$ts, $this, $relativeTo, $user, $lang ) ) ) {
+ if ( Hooks::run( 'GetHumanTimestamp', array( &$ts, $this, $relativeTo, $user, $lang ) ) ) {
$ts = $lang->getHumanTimestamp( $this, $relativeTo, $user );
}
@@ -326,7 +331,7 @@ class MWTimestamp {
$ts = '';
$diff = $this->diff( $relativeTo );
- if ( wfRunHooks(
+ if ( Hooks::run(
'GetRelativeTimestamp',
array( &$ts, &$diff, $this, $relativeTo, $user, $lang )
) ) {
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 4d17298b..186821de 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -172,7 +172,6 @@ class MagicWord {
'directionmark',
'contentlanguage',
'numberofadmins',
- 'numberofviews',
'cascadingsources',
);
@@ -215,7 +214,6 @@ class MagicWord {
'localtimestamp' => 3600,
'pagesinnamespace' => 3600,
'numberofadmins' => 3600,
- 'numberofviews' => 3600,
'numberingroup' => 3600,
);
@@ -275,7 +273,7 @@ class MagicWord {
static function getVariableIDs() {
if ( !self::$mVariableIDsInitialised ) {
# Get variable IDs
- wfRunHooks( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) );
+ Hooks::run( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) );
self::$mVariableIDsInitialised = true;
}
return self::$mVariableIDs;
@@ -310,7 +308,7 @@ class MagicWord {
*/
static function getDoubleUnderscoreArray() {
if ( is_null( self::$mDoubleUnderscoreArray ) ) {
- wfRunHooks( 'GetDoubleUnderscoreIDs', array( &self::$mDoubleUnderscoreIDs ) );
+ Hooks::run( 'GetDoubleUnderscoreIDs', array( &self::$mDoubleUnderscoreIDs ) );
self::$mDoubleUnderscoreArray = new MagicWordArray( self::$mDoubleUnderscoreIDs );
}
return self::$mDoubleUnderscoreArray;
@@ -332,15 +330,12 @@ class MagicWord {
*/
function load( $id ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
$this->mId = $id;
$wgContLang->getMagic( $this );
if ( !$this->mSynonyms ) {
$this->mSynonyms = array( 'brionmademeputthishere' );
- wfProfileOut( __METHOD__ );
throw new MWException( "Error: invalid magic word '$id'" );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -657,7 +652,7 @@ class MagicWord {
* This method uses the php feature to do several replacements at the same time,
* thereby gaining some efficiency. The result is placed in the out variable
* $result. The return value is true if something was replaced.
- * @todo Should this be static? It doesn't seem to be used at all
+ * @deprecated since 1.25, unused
*
* @param array $magicarr
* @param string $subject
@@ -666,6 +661,7 @@ class MagicWord {
* @return bool
*/
function replaceMultiple( $magicarr, $subject, &$result ) {
+ wfDeprecated( __METHOD__, '1.25' );
$search = array();
$replace = array();
foreach ( $magicarr as $id => $replacement ) {
diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php
index 402494ec..ec2f40f6 100644
--- a/includes/MediaWiki.php
+++ b/includes/MediaWiki.php
@@ -20,10 +20,10 @@
* @file
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* The MediaWiki class is the helper class for the index.php entry point.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class MediaWiki {
/**
@@ -59,7 +59,7 @@ class MediaWiki {
$request = $this->context->getRequest();
$curid = $request->getInt( 'curid' );
$title = $request->getVal( 'title' );
- $action = $request->getVal( 'action', 'view' );
+ $action = $request->getVal( 'action' );
if ( $request->getCheck( 'search' ) ) {
// Compatibility with old search URLs which didn't use Special:Search
@@ -121,7 +121,7 @@ class MediaWiki {
* @return Title
*/
public function getTitle() {
- if ( $this->context->getTitle() === null ) {
+ if ( !$this->context->hasTitle() ) {
$this->context->setTitle( $this->parseTitle() );
}
return $this->context->getTitle();
@@ -157,8 +157,6 @@ class MediaWiki {
private function performRequest() {
global $wgTitle;
- wfProfileIn( __METHOD__ );
-
$request = $this->context->getRequest();
$requestTitle = $title = $this->context->getTitle();
$output = $this->context->getOutput();
@@ -169,14 +167,13 @@ class MediaWiki {
}
$unused = null; // To pass it by reference
- wfRunHooks( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) );
+ Hooks::run( '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->isExternal() )
|| $title->isSpecial( 'Badtitle' )
) {
$this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
- wfProfileOut( __METHOD__ );
throw new BadTitleError();
}
@@ -201,12 +198,9 @@ class MediaWiki {
$this->context->setTitle( $badTitle );
$wgTitle = $badTitle;
- wfProfileOut( __METHOD__ );
throw new PermissionsError( 'read', $permErrors );
}
- $pageView = false; // was an article or special page viewed?
-
// Interwiki redirects
if ( $title->isExternal() ) {
$rdfrom = $request->getVal( 'rdfrom' );
@@ -225,7 +219,6 @@ class MediaWiki {
$output->redirect( $url, 301 );
} else {
$this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
- wfProfileOut( __METHOD__ );
throw new BadTitleError();
}
// Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
@@ -233,7 +226,7 @@ class MediaWiki {
&& ( $request->getVal( 'title' ) === null
|| $title->getPrefixedDBkey() != $request->getVal( 'title' ) )
&& !count( $request->getValueNames( array( 'action', 'title' ) ) )
- && wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) )
+ && Hooks::run( 'TestCanonicalRedirect', array( $request, $title, $output ) )
) {
if ( $title->isSpecialPage() ) {
list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
@@ -270,7 +263,6 @@ class MediaWiki {
}
// Special pages
} elseif ( NS_SPECIAL == $title->getNamespace() ) {
- $pageView = true;
// Actions that need to be made when we have a special pages
SpecialPageFactory::executePath( $title, $this->context );
} else {
@@ -278,23 +270,14 @@ class MediaWiki {
// may be a redirect to another article or URL.
$article = $this->initializeArticle();
if ( is_object( $article ) ) {
- $pageView = true;
$this->performAction( $article, $requestTitle );
} elseif ( is_string( $article ) ) {
$output->redirect( $article );
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
. " returned neither an object nor a URL" );
}
}
-
- if ( $pageView ) {
- // Promote user to any groups they meet the criteria for
- $user->addAutopromoteOnceGroups( 'onView' );
- }
-
- wfProfileOut( __METHOD__ );
}
/**
@@ -304,7 +287,6 @@ class MediaWiki {
* @return mixed An Article, or a string to redirect to another URL
*/
private function initializeArticle() {
- wfProfileIn( __METHOD__ );
$title = $this->context->getTitle();
if ( $this->context->canUseWikiPage() ) {
@@ -322,7 +304,6 @@ class MediaWiki {
// NS_MEDIAWIKI has no redirects.
// It is also used for CSS/JS, so performance matters here...
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
- wfProfileOut( __METHOD__ );
return $article;
}
@@ -342,7 +323,7 @@ class MediaWiki {
// Give extensions a change to ignore/handle redirects as needed
$ignoreRedirect = $target = false;
- wfRunHooks( 'InitializeArticleMaybeRedirect',
+ Hooks::run( 'InitializeArticleMaybeRedirect',
array( &$title, &$request, &$ignoreRedirect, &$target, &$article ) );
// Follow redirects only for... redirects.
@@ -353,7 +334,6 @@ class MediaWiki {
if ( is_string( $target ) ) {
if ( !$this->config->get( 'DisableHardRedirects' ) ) {
// we'll need to redirect
- wfProfileOut( __METHOD__ );
return $target;
}
}
@@ -374,7 +354,6 @@ class MediaWiki {
}
}
- wfProfileOut( __METHOD__ );
return $article;
}
@@ -385,17 +364,15 @@ class MediaWiki {
* @param Title $requestTitle The original title, before any redirects were applied
*/
private function performAction( Page $page, Title $requestTitle ) {
- wfProfileIn( __METHOD__ );
$request = $this->context->getRequest();
$output = $this->context->getOutput();
$title = $this->context->getTitle();
$user = $this->context->getUser();
- if ( !wfRunHooks( 'MediaWikiPerformAction',
+ if ( !Hooks::run( 'MediaWikiPerformAction',
array( $output, $page, $title, $user, $request, $this ) )
) {
- wfProfileOut( __METHOD__ );
return;
}
@@ -406,22 +383,24 @@ class MediaWiki {
if ( $action instanceof Action ) {
# Let Squid cache things if we can purge them.
if ( $this->config->get( 'UseSquid' ) &&
- in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() )
+ in_array(
+ // Use PROTO_INTERNAL because that's what getSquidURLs() uses
+ wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ),
+ $requestTitle->getSquidURLs()
+ )
) {
$output->setSquidMaxage( $this->config->get( 'SquidMaxage' ) );
}
$action->show();
- wfProfileOut( __METHOD__ );
return;
}
- if ( wfRunHooks( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) {
+ if ( Hooks::run( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) {
$output->setStatusCode( 404 );
$output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -446,7 +425,7 @@ class MediaWiki {
$this->triggerJobs();
$this->restInPeace();
} catch ( Exception $e ) {
- MWExceptionHandler::handle( $e );
+ MWExceptionHandler::handleException( $e );
}
}
@@ -456,7 +435,6 @@ class MediaWiki {
* @return bool
*/
private function checkMaxLag() {
- wfProfileIn( __METHOD__ );
$maxLag = $this->context->getRequest()->getVal( 'maxlag' );
if ( !is_null( $maxLag ) ) {
list( $host, $lag ) = wfGetLB()->getMaxLag();
@@ -472,33 +450,28 @@ class MediaWiki {
echo "Waiting for a database server: $lag seconds lagged\n";
}
- wfProfileOut( __METHOD__ );
-
exit;
}
}
- wfProfileOut( __METHOD__ );
return true;
}
private function main() {
global $wgTitle;
- wfProfileIn( __METHOD__ );
-
$request = $this->context->getRequest();
// Send Ajax requests to the Ajax dispatcher.
- if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action', 'view' ) == 'ajax' ) {
-
+ if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action' ) === 'ajax' ) {
// Set a dummy title, because $wgTitle == null might break things
- $title = Title::makeTitle( NS_MAIN, 'AJAX' );
+ $title = Title::makeTitle( NS_SPECIAL, 'Badtitle/performing an AJAX call in '
+ . __METHOD__
+ );
$this->context->setTitle( $title );
$wgTitle = $title;
$dispatcher = new AjaxDispatcher( $this->config );
$dispatcher->performAction( $this->context->getUser() );
- wfProfileOut( __METHOD__ );
return;
}
@@ -508,6 +481,20 @@ class MediaWiki {
$action = $this->getAction();
$wgTitle = $title;
+ $trxProfiler = Profiler::instance()->getTransactionProfiler();
+ $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
+
+ // Aside from rollback, master queries should not happen on GET requests.
+ // Periodic or "in passing" updates on GET should use the job queue.
+ if ( !$request->wasPosted()
+ && in_array( $action, array( 'view', 'edit', 'history' ) )
+ ) {
+ $trxProfiler->setExpectation( 'masterConns', 0, __METHOD__ );
+ $trxProfiler->setExpectation( 'writes', 0, __METHOD__ );
+ } else {
+ $trxProfiler->setExpectation( 'maxAffected', 500, __METHOD__ );
+ }
+
// If the user has forceHTTPS set to true, or if the user
// is in a group requiring HTTPS, or if they have the HTTPS
// preference set, redirect them to HTTPS.
@@ -530,7 +517,7 @@ class MediaWiki {
$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 ( Hooks::run( 'BeforeHttpsRedirect', array( $this->context, &$redirUrl ) ) ) {
if ( $request->wasPosted() ) {
// This is weird and we'd hope it almost never happens. This
@@ -544,20 +531,18 @@ class MediaWiki {
wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" );
}
// Setup dummy Title, otherwise OutputPage::redirect will fail
- $title = Title::newFromText( NS_MAIN, 'REDIR' );
+ $title = Title::newFromText( 'REDIR', NS_MAIN );
$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 ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) {
- wfProfileIn( 'main-try-filecache' );
if ( HTMLFileCache::useFileCache( $this->context ) ) {
// Try low-level file cache hit
$cache = new HTMLFileCache( $title, $action );
@@ -572,12 +557,9 @@ class MediaWiki {
$this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
// Tell OutputPage that output is taken care of
$this->context->getOutput()->disable();
- wfProfileOut( 'main-try-filecache' );
- wfProfileOut( __METHOD__ );
return;
}
}
- wfProfileOut( 'main-try-filecache' );
}
// Actually do the work of the request and build up any output
@@ -593,13 +575,16 @@ class MediaWiki {
// Output everything!
$this->context->getOutput()->output();
- wfProfileOut( __METHOD__ );
}
/**
* Ends this task peacefully
*/
public function restInPeace() {
+ // Ignore things like master queries/connections on GET requests
+ // as long as they are in deferred updates (which catch errors).
+ Profiler::instance()->getTransactionProfiler()->resetExpectations();
+
// Do any deferred jobs
DeferredUpdates::doUpdates( 'commit' );
@@ -627,8 +612,6 @@ class MediaWiki {
return; // recursion guard
}
- $section = new ProfileSection( __METHOD__ );
-
if ( $jobRunRate < 1 ) {
$max = mt_getrandmax();
if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
@@ -639,9 +622,11 @@ class MediaWiki {
$n = intval( $jobRunRate );
}
+ $runJobsLogger = LoggerFactory::getInstance( 'runJobs' );
+
if ( !$this->config->get( 'RunJobsAsync' ) ) {
// Fall back to running the job here while the user waits
- $runner = new JobRunner();
+ $runner = new JobRunner( $runJobsLogger );
$runner->run( array( 'maxJobs' => $n ) );
return;
}
@@ -674,29 +659,34 @@ class MediaWiki {
);
wfRestoreWarnings();
if ( !$sock ) {
- wfDebugLog( 'runJobs', "Failed to start cron API (socket error $errno): $errstr\n" );
+ $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" );
// Fall back to running the job here while the user waits
- $runner = new JobRunner();
+ $runner = new JobRunner( $runJobsLogger );
$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";
+ $req = (
+ "POST $url HTTP/1.1\r\n" .
+ "Host: {$info['host']}\r\n" .
+ "Connection: Close\r\n" .
+ "Content-Length: 0\r\n\r\n"
+ );
- wfDebugLog( 'runJobs', "Running $n job(s) via '$url'\n" );
+ $runJobsLogger->info( "Running $n job(s) via '$url'" );
// 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" );
+ $runJobsLogger->error( "Failed to start cron API (socket write error)" );
} else {
// 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" );
+ $runJobsLogger->error( "Failed to start cron API: received '$status'" );
}
}
fclose( $sock );
diff --git a/includes/MediaWikiVersionFetcher.php b/includes/MediaWikiVersionFetcher.php
index 439e53f4..943bc9fc 100644
--- a/includes/MediaWikiVersionFetcher.php
+++ b/includes/MediaWikiVersionFetcher.php
@@ -4,7 +4,6 @@
* 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 {
@@ -19,7 +18,7 @@ class MediaWikiVersionFetcher {
$defaultSettings = file_get_contents( __DIR__ . '/DefaultSettings.php' );
$matches = array();
- preg_match( "/wgVersion = '([-0-9a-zA-Z\.]+)';/", $defaultSettings, $matches );
+ 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' );
diff --git a/includes/Message.php b/includes/Message.php
index 4df0d809..134af0ed 100644
--- a/includes/Message.php
+++ b/includes/Message.php
@@ -156,7 +156,7 @@
*
* @since 1.17
*/
-class Message {
+class Message implements MessageSpecifier {
/**
* In which language to get this message. True, which is the default,
@@ -249,7 +249,7 @@ class Message {
$this->key = reset( $this->keysToTry );
$this->parameters = array_values( $params );
- $this->language = $language ? $language : $wgLang;
+ $this->language = $language ?: $wgLang;
}
/**
@@ -276,7 +276,7 @@ class Message {
* 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
+ * return any of these keys. After the message has been fetched, this method will return
* the key that was actually used to fetch the message.
*
* @since 1.21
@@ -541,6 +541,30 @@ class Message {
}
/**
+ * Add parameters that are plaintext and will be passed through without
+ * the content being evaluated. Plaintext parameters are not valid as
+ * arguments to parser functions. This differs from self::rawParams in
+ * that the Message class handles escaping to match the output format.
+ *
+ * @since 1.25
+ *
+ * @param string|string[] $param,... plaintext parameters, or a single argument that is
+ * an array of plaintext parameters.
+ *
+ * @return Message $this
+ */
+ public function plaintextParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::plaintextParam( $param );
+ }
+ return $this;
+ }
+
+ /**
* Set the language and the title from a context object
*
* @since 1.19
@@ -674,11 +698,10 @@ class Message {
$string = $this->fetchMessage();
if ( $string === false ) {
- $key = htmlspecialchars( $this->key );
- if ( $this->format === 'plain' ) {
- return '<' . $key . '>';
+ if ( $this->format === 'plain' || $this->format === 'text' ) {
+ return '<' . $this->key . '>';
}
- return '&lt;' . $key . '&gt;';
+ return '&lt;' . htmlspecialchars( $this->key ) . '&gt;';
}
# Replace $* with a list of parameters for &uselang=qqx.
@@ -735,10 +758,10 @@ class Message {
// Doh! Cause a fatal error after all?
}
- if ( $this->format === 'plain' ) {
+ if ( $this->format === 'plain' || $this->format === 'text' ) {
return '<' . $this->key . '>';
}
- return '&lt;' . $this->key . '&gt;';
+ return '&lt;' . htmlspecialchars( $this->key ) . '&gt;';
}
}
@@ -917,6 +940,17 @@ class Message {
}
/**
+ * @since 1.25
+ *
+ * @param string $plaintext
+ *
+ * @return string[] Array with a single "plaintext" key.
+ */
+ public static function plaintextParam( $plaintext ) {
+ return array( 'plaintext' => $plaintext );
+ }
+
+ /**
* Substitutes any parameters into the message text.
*
* @since 1.17
@@ -965,6 +999,8 @@ class Message {
return array( 'before', $this->language->formatSize( $param['size'] ) );
} elseif ( isset( $param['bitrate'] ) ) {
return array( 'before', $this->language->formatBitrate( $param['bitrate'] ) );
+ } elseif ( isset( $param['plaintext'] ) ) {
+ return array( 'after', $this->formatPlaintext( $param['plaintext'] ) );
} else {
$warning = 'Invalid parameter for message "' . $this->getKey() . '": ' .
htmlspecialchars( serialize( $param ) );
@@ -1050,6 +1086,31 @@ class Message {
return $this->message;
}
+ /**
+ * Formats a message parameter wrapped with 'plaintext'. Ensures that
+ * the entire string is displayed unchanged when displayed in the output
+ * format.
+ *
+ * @since 1.25
+ *
+ * @param string $plaintext String to ensure plaintext output of
+ *
+ * @return string Input plaintext encoded for output to $this->format
+ */
+ protected function formatPlaintext( $plaintext ) {
+ switch ( $this->format ) {
+ case 'text':
+ case 'plain':
+ return $plaintext;
+
+ case 'parse':
+ case 'block-parse':
+ case 'escaped':
+ default:
+ return htmlspecialchars( $plaintext, ENT_QUOTES );
+
+ }
+ }
}
/**
diff --git a/includes/MessageBlobStore.php b/includes/MessageBlobStore.php
index e3b4dbe8..011cae66 100644
--- a/includes/MessageBlobStore.php
+++ b/includes/MessageBlobStore.php
@@ -36,15 +36,12 @@ class MessageBlobStore {
* Get the singleton instance
*
* @since 1.24
+ * @deprecated since 1.25
* @return MessageBlobStore
*/
public static function getInstance() {
- static $instance = null;
- if ( $instance === null ) {
- $instance = new self;
- }
-
- return $instance;
+ wfDeprecated( __METHOD__, '1.25' );
+ return new self;
}
/**
@@ -56,9 +53,7 @@ class MessageBlobStore {
* @return array An array mapping module names to message blobs
*/
public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
- wfProfileIn( __METHOD__ );
if ( !count( $modules ) ) {
- wfProfileOut( __METHOD__ );
return array();
}
// Try getting from the DB first
@@ -73,7 +68,6 @@ class MessageBlobStore {
}
}
- wfProfileOut( __METHOD__ );
return $blobs;
}
@@ -130,7 +124,7 @@ class MessageBlobStore {
);
}
}
- } catch ( Exception $e ) {
+ } catch ( DBError $e ) {
wfDebug( __METHOD__ . " failed to update DB: $e\n" );
}
return $blob;
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index bfd60111..ebe98a3c 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -200,7 +200,7 @@ class MimeMagic {
global $IP;
# Allow media handling extensions adding MIME-types and MIME-info
- wfRunHooks( 'MimeMagicInit', array( $this ) );
+ Hooks::run( 'MimeMagicInit', array( $this ) );
$types = MM_WELL_KNOWN_MIME_TYPES;
@@ -210,7 +210,7 @@ class MimeMagic {
}
if ( $mimeTypeFile ) {
- if ( is_file( $mimeTypeFile ) and is_readable( $mimeTypeFile ) ) {
+ if ( is_file( $mimeTypeFile ) && is_readable( $mimeTypeFile ) ) {
wfDebug( __METHOD__ . ": loading mime types from $mimeTypeFile\n" );
$types .= "\n";
$types .= file_get_contents( $mimeTypeFile );
@@ -218,7 +218,7 @@ class MimeMagic {
wfDebug( __METHOD__ . ": can't load mime types from $mimeTypeFile\n" );
}
} else {
- wfDebug( __METHOD__ . ": no mime types file defined, using build-ins only.\n" );
+ wfDebug( __METHOD__ . ": no mime types file defined, using built-ins only.\n" );
}
$types .= "\n" . $this->mExtraTypes;
@@ -287,7 +287,7 @@ class MimeMagic {
$info = MM_WELL_KNOWN_MIME_INFO;
if ( $mimeInfoFile ) {
- if ( is_file( $mimeInfoFile ) and is_readable( $mimeInfoFile ) ) {
+ if ( is_file( $mimeInfoFile ) && is_readable( $mimeInfoFile ) ) {
wfDebug( __METHOD__ . ": loading mime info from $mimeInfoFile\n" );
$info .= "\n";
$info .= file_get_contents( $mimeInfoFile );
@@ -295,7 +295,7 @@ class MimeMagic {
wfDebug( __METHOD__ . ": can't load mime info from $mimeInfoFile\n" );
}
} else {
- wfDebug( __METHOD__ . ": no mime info file defined, using build-ins only.\n" );
+ wfDebug( __METHOD__ . ": no mime info file defined, using built-ins only.\n" );
}
$info .= "\n" . $this->mExtraInfo;
@@ -569,7 +569,7 @@ class MimeMagic {
}
# Media handling extensions can improve the MIME detected
- wfRunHooks( 'MimeMagicImproveFromExtension', array( $this, $ext, &$mime ) );
+ Hooks::run( 'MimeMagicImproveFromExtension', array( $this, $ext, &$mime ) );
if ( isset( $this->mMimeTypeAliases[$mime] ) ) {
$mime = $this->mMimeTypeAliases[$mime];
@@ -802,7 +802,7 @@ class MimeMagic {
# people will hopefully nag and submit patches :)
$mime = false;
# Some strings by reference for performance - assuming well-behaved hooks
- wfRunHooks(
+ Hooks::run(
'MimeMagicGuessFromContent',
array( $this, &$head, &$tail, $file, &$mime )
);
diff --git a/includes/MovePage.php b/includes/MovePage.php
index fdece8d5..de7da3f9 100644
--- a/includes/MovePage.php
+++ b/includes/MovePage.php
@@ -42,6 +42,188 @@ class MovePage {
$this->newTitle = $newTitle;
}
+ public function checkPermissions( User $user, $reason ) {
+ $status = new Status();
+
+ $errors = wfMergeErrorArrays(
+ $this->oldTitle->getUserPermissionsErrors( 'move', $user ),
+ $this->oldTitle->getUserPermissionsErrors( 'edit', $user ),
+ $this->newTitle->getUserPermissionsErrors( 'move-target', $user ),
+ $this->newTitle->getUserPermissionsErrors( 'edit', $user )
+ );
+
+ // Convert into a Status object
+ if ( $errors ) {
+ foreach ( $errors as $error ) {
+ call_user_func_array( array( $status, 'fatal' ), $error );
+ }
+ }
+
+ if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) {
+ // This is kind of lame, won't display nice
+ $status->fatal( 'spamprotectiontext' );
+ }
+
+ # The move is allowed only if (1) the target doesn't exist, or
+ # (2) the target is a redirect to the source, and has no history
+ # (so we can undo bad moves right after they're done).
+
+ if ( $this->newTitle->getArticleID() ) { # Target exists; check for validity
+ if ( !$this->isValidMoveTarget() ) {
+ $status->fatal( 'articleexists' );
+ }
+ } else {
+ $tp = $this->newTitle->getTitleProtection();
+ if ( $tp !== false ) {
+ if ( !$user->isAllowed( $tp['permission'] ) ) {
+ $status->fatal( 'cantmove-titleprotected' );
+ }
+ }
+ }
+
+ Hooks::run( 'MovePageCheckPermissions',
+ array( $this->oldTitle, $this->newTitle, $user, $reason, $status )
+ );
+
+ return $status;
+ }
+
+ /**
+ * Does various sanity checks that the move is
+ * valid. Only things based on the two titles
+ * should be checked here.
+ *
+ * @return Status
+ */
+ public function isValidMove() {
+ global $wgContentHandlerUseDB;
+ $status = new Status();
+
+ if ( $this->oldTitle->equals( $this->newTitle ) ) {
+ $status->fatal( 'selfmove' );
+ }
+ if ( !$this->oldTitle->isMovable() ) {
+ $status->fatal( 'immobile-source-namespace', $this->oldTitle->getNsText() );
+ }
+ if ( $this->newTitle->isExternal() ) {
+ $status->fatal( 'immobile-target-namespace-iw' );
+ }
+ if ( !$this->newTitle->isMovable() ) {
+ $status->fatal( 'immobile-target-namespace', $this->newTitle->getNsText() );
+ }
+
+ $oldid = $this->oldTitle->getArticleID();
+
+ if ( strlen( $this->newTitle->getDBkey() ) < 1 ) {
+ $status->fatal( 'articleexists' );
+ }
+ if (
+ ( $this->oldTitle->getDBkey() == '' ) ||
+ ( !$oldid ) ||
+ ( $this->newTitle->getDBkey() == '' )
+ ) {
+ $status->fatal( 'badarticleerror' );
+ }
+
+ // Content model checks
+ if ( !$wgContentHandlerUseDB &&
+ $this->oldTitle->getContentModel() !== $this->newTitle->getContentModel() ) {
+ // can't move a page if that would change the page's content model
+ $status->fatal(
+ 'bad-target-model',
+ ContentHandler::getLocalizedName( $this->oldTitle->getContentModel() ),
+ ContentHandler::getLocalizedName( $this->newTitle->getContentModel() )
+ );
+ }
+
+ // Image-specific checks
+ if ( $this->oldTitle->inNamespace( NS_FILE ) ) {
+ $status->merge( $this->isValidFileMove() );
+ }
+
+ if ( $this->newTitle->inNamespace( NS_FILE ) && !$this->oldTitle->inNamespace( NS_FILE ) ) {
+ $status->fatal( 'nonfile-cannot-move-to-file' );
+ }
+
+ // Hook for extensions to say a title can't be moved for technical reasons
+ Hooks::run( 'MovePageIsValidMove', array( $this->oldTitle, $this->newTitle, $status ) );
+
+ return $status;
+ }
+
+ /**
+ * Sanity checks for when a file is being moved
+ *
+ * @return Status
+ */
+ protected function isValidFileMove() {
+ $status = new Status();
+ $file = wfLocalFile( $this->oldTitle );
+ $file->load( File::READ_LATEST );
+ if ( $file->exists() ) {
+ if ( $this->newTitle->getText() != wfStripIllegalFilenameChars( $this->newTitle->getText() ) ) {
+ $status->fatal( 'imageinvalidfilename' );
+ }
+ if ( !File::checkExtensionCompatibility( $file, $this->newTitle->getDBkey() ) ) {
+ $status->fatal( 'imagetypemismatch' );
+ }
+ }
+
+ if ( !$this->newTitle->inNamespace( NS_FILE ) ) {
+ $status->fatal( 'imagenocrossnamespace' );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Checks if $this can be moved to a given Title
+ * - Selects for update, so don't call it unless you mean business
+ *
+ * @since 1.25
+ * @return bool
+ */
+ protected function isValidMoveTarget() {
+ # Is it an existing file?
+ if ( $this->newTitle->inNamespace( NS_FILE ) ) {
+ $file = wfLocalFile( $this->newTitle );
+ $file->load( File::READ_LATEST );
+ if ( $file->exists() ) {
+ wfDebug( __METHOD__ . ": file exists\n" );
+ return false;
+ }
+ }
+ # Is it a redirect with no history?
+ if ( !$this->newTitle->isSingleRevRedirect() ) {
+ wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
+ return false;
+ }
+ # Get the article text
+ $rev = Revision::newFromTitle( $this->newTitle, false, Revision::READ_LATEST );
+ if ( !is_object( $rev ) ) {
+ return false;
+ }
+ $content = $rev->getContent();
+ # Does the redirect point to the source?
+ # Or is it a broken self-redirect, usually caused by namespace collisions?
+ $redirTitle = $content ? $content->getRedirectTarget() : null;
+
+ if ( $redirTitle ) {
+ if ( $redirTitle->getPrefixedDBkey() !== $this->oldTitle->getPrefixedDBkey() &&
+ $redirTitle->getPrefixedDBkey() !== $this->newTitle->getPrefixedDBkey() ) {
+ wfDebug( __METHOD__ . ": redirect points to other page\n" );
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ # Fail safe (not a redirect after all. strange.)
+ wfDebug( __METHOD__ . ": failsafe: database says " . $this->newTitle->getPrefixedDBkey() .
+ " is a redirect, but it doesn't contain a valid redirect.\n" );
+ return false;
+ }
+ }
+
/**
* @param User $user
* @param string $reason
@@ -51,11 +233,14 @@ class MovePage {
public function move( User $user, $reason, $createRedirect ) {
global $wgCategoryCollation;
+ Hooks::run( 'TitleMove', array( $this->oldTitle, $this->newTitle, $user ) );
+
// 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 );
+ $file->load( File::READ_LATEST );
if ( $file->exists() ) {
$status = $file->move( $this->newTitle );
if ( !$status->isOk() ) {
@@ -188,9 +373,11 @@ class MovePage {
$dbw->commit( __METHOD__ );
- wfRunHooks( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) );
+ Hooks::run(
+ 'TitleMoveComplete',
+ array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason )
+ );
return Status::newGood();
-
}
/**
@@ -258,6 +445,9 @@ class MovePage {
$dbw = wfGetDB( DB_MASTER );
+ $oldpage = WikiPage::factory( $this->oldTitle );
+ $oldcountable = $oldpage->isCountable();
+
$newpage = WikiPage::factory( $nt );
if ( $moveOverRedirect ) {
@@ -302,10 +492,11 @@ class MovePage {
$newpage->updateRevisionOn( $dbw, $nullRevision );
- wfRunHooks( 'NewRevisionFromEditComplete',
+ Hooks::run( 'NewRevisionFromEditComplete',
array( $newpage, $nullRevision, $nullRevision->getParentId(), $user ) );
- $newpage->doEditUpdates( $nullRevision, $user, array( 'changed' => false ) );
+ $newpage->doEditUpdates( $nullRevision, $user,
+ array( 'changed' => false, 'moved' => true, 'oldcountable' => $oldcountable ) );
if ( !$moveOverRedirect ) {
WikiPage::onArticleCreate( $nt );
@@ -328,7 +519,7 @@ class MovePage {
$redirectRevision->insertOn( $dbw );
$redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
- wfRunHooks( 'NewRevisionFromEditComplete',
+ Hooks::run( 'NewRevisionFromEditComplete',
array( $redirectArticle, $redirectRevision, false, $user ) );
$redirectArticle->doEditUpdates( $redirectRevision, $user, array( 'created' => true ) );
@@ -339,5 +530,4 @@ class MovePage {
$logid = $logEntry->insert();
$logEntry->publish( $logid );
}
-
-} \ No newline at end of file
+}
diff --git a/includes/templates/NoLocalSettings.php b/includes/NoLocalSettings.php
index 5b88dfd1..6de9bfcd 100644
--- a/includes/templates/NoLocalSettings.php
+++ b/includes/NoLocalSettings.php
@@ -1,7 +1,6 @@
<?php
-// @codingStandardsIgnoreFile
/**
- * Template used when there is no LocalSettings.php file.
+ * Display an error page when there is no LocalSettings.php file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,17 +18,8 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup Templates
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- die( "NoLocalSettings.php is not a valid MediaWiki entry point\n" );
-}
-
-if ( !isset( $wgVersion ) ) {
- $wgVersion = 'VERSION';
-}
-
# bug 30219 : can not use pathinfo() on URLs since slashes do not match
$matches = array();
$ext = 'php';
@@ -39,6 +29,7 @@ foreach ( array_filter( explode( '/', $_SERVER['PHP_SELF'] ) ) as $part ) {
$path .= "$part/";
} else {
$ext = $matches[1] == 'php5' ? 'php5' : 'php';
+ break;
}
}
@@ -52,46 +43,21 @@ if ( !function_exists( 'session_name' ) ) {
error_reporting( $oldReporting );
$installerStarted = ( $success && isset( $_SESSION['installData'] ) );
}
-?>
-<!DOCTYPE html>
-<html lang="en" dir="ltr">
- <head>
- <meta charset="UTF-8" />
- <title>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></title>
- <style media='screen'>
- html, body {
- color: #000;
- background-color: #fff;
- font-family: sans-serif;
- text-align: center;
- }
-
- h1 {
- font-size: 150%;
- }
- </style>
- </head>
- <body>
- <img src="<?php echo htmlspecialchars( $path ) ?>resources/assets/mediawiki.png" alt='The MediaWiki logo' />
- <h1>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></h1>
- <div class='error'>
- <?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 } ?>
+$templateParser = new TemplateParser();
- </div>
- </body>
-</html>
+# Render error page if no LocalSettings file can be found
+try {
+ echo $templateParser->processTemplate(
+ 'NoLocalSettings',
+ array(
+ 'wgVersion' => ( isset( $wgVersion ) ? $wgVersion : 'VERSION' ),
+ 'path' => $path,
+ 'ext' => $ext,
+ 'localSettingsExists' => file_exists( MW_CONFIG_FILE ),
+ 'installerStarted' => $installerStarted
+ )
+ );
+} catch ( Exception $e ) {
+ echo 'Error: ' . htmlspecialchars( $e->getMessage() );
+}
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
index b0bbcddb..c6209eeb 100644
--- a/includes/OutputHandler.php
+++ b/includes/OutputHandler.php
@@ -129,7 +129,8 @@ function wfGzipHandler( $s ) {
$headers = headers_list();
$foundVary = false;
foreach ( $headers as $header ) {
- if ( substr( $header, 0, 5 ) == 'Vary:' ) {
+ $headerName = strtolower( substr( $header, 0, 5 ) );
+ if ( $headerName == 'vary:' ) {
$foundVary = true;
break;
}
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 55b1da00..7e671878 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -122,6 +122,9 @@ class OutputPage extends ContextSource {
/** @var array */
protected $mCategories = array();
+ /** @var array */
+ protected $mIndicators = array();
+
/** @var array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page') */
private $mLanguageLinks = array();
@@ -193,12 +196,6 @@ class OutputPage extends ContextSource {
// Parser related.
- /**
- * @var int
- * @todo Unused?
- */
- private $mContainsOldMagic = 0;
-
/** @var int */
protected $mContainsNewMagic = 0;
@@ -244,8 +241,9 @@ class OutputPage extends ContextSource {
protected $mSquidMaxage = 0;
/**
- * @var bool
- * @todo Document
+ * @var bool Controls if anti-clickjacking / frame-breaking headers will
+ * be sent. This should be done for pages where edit actions are possible.
+ * Setters: $this->preventClickjacking() and $this->allowClickjacking().
*/
protected $mPreventClickjacking = true;
@@ -783,7 +781,7 @@ class OutputPage extends ContextSource {
// bug 44570: the core page itself may not change, but resources might
$modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
}
- wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
+ Hooks::run( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
$maxModified = max( $modifiedTimes );
$this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
@@ -1030,17 +1028,29 @@ class OutputPage extends ContextSource {
}
/**
- * Add a subtitle containing a backlink to a page
+ * Build message object for a subtitle containing a backlink to a page
*
* @param Title $title Title to link to
* @param array $query Array of additional parameters to include in the link
+ * @return Message
+ * @since 1.25
*/
- public function addBacklinkSubtitle( Title $title, $query = array() ) {
+ public static function buildBacklinkSubtitle( Title $title, $query = array() ) {
if ( $title->isRedirect() ) {
$query['redirect'] = 'no';
}
- $this->addSubtitle( $this->msg( 'backlinksubtitle' )
- ->rawParams( Linker::link( $title, null, array(), $query ) ) );
+ return wfMessage( 'backlinksubtitle' )
+ ->rawParams( Linker::link( $title, null, array(), $query ) );
+ }
+
+ /**
+ * Add a subtitle containing a backlink to a page
+ *
+ * @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() ) {
+ $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
}
/**
@@ -1060,7 +1070,7 @@ class OutputPage extends ContextSource {
}
/**
- * Set the page as printable, i.e. it'll be displayed with with all
+ * Set the page as printable, i.e. it'll be displayed with all
* print styles included
*/
public function setPrintable() {
@@ -1310,7 +1320,7 @@ class OutputPage extends ContextSource {
}
# Add the remaining categories to the skin
- if ( wfRunHooks(
+ if ( Hooks::run(
'OutputPageMakeCategoryLinks',
array( &$this, $categories, &$this->mCategoryLinks ) )
) {
@@ -1363,6 +1373,65 @@ class OutputPage extends ContextSource {
}
/**
+ * Add an array of indicators, with their identifiers as array
+ * keys and HTML contents as values.
+ *
+ * In case of duplicate keys, existing values are overwritten.
+ *
+ * @param array $indicators
+ * @since 1.25
+ */
+ public function setIndicators( array $indicators ) {
+ $this->mIndicators = $indicators + $this->mIndicators;
+ // Keep ordered by key
+ ksort( $this->mIndicators );
+ }
+
+ /**
+ * Get the indicators associated with this page.
+ *
+ * The array will be internally ordered by item keys.
+ *
+ * @return array Keys: identifiers, values: HTML contents
+ * @since 1.25
+ */
+ public function getIndicators() {
+ return $this->mIndicators;
+ }
+
+ /**
+ * Adds help link with an icon via page indicators.
+ * Link target can be overridden by a local message containing a wikilink:
+ * the message key is: lowercase action or special page name + '-helppage'.
+ * @param string $to Target MediaWiki.org page title or encoded URL.
+ * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o.
+ * @since 1.25
+ */
+ public function addHelpLink( $to, $overrideBaseUrl = false ) {
+ $this->addModuleStyles( 'mediawiki.helplink' );
+ $text = $this->msg( 'helppage-top-gethelp' )->escaped();
+
+ if ( $overrideBaseUrl ) {
+ $helpUrl = $to;
+ } else {
+ $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
+ $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
+ }
+
+ $link = Html::rawElement(
+ 'a',
+ array(
+ 'href' => $helpUrl,
+ 'target' => '_blank',
+ 'class' => 'mw-helplink',
+ ),
+ $text
+ );
+
+ $this->setIndicators( array( 'mw-helplink' => $link ) );
+ }
+
+ /**
* Do not allow scripts which can be modified by wiki users to load on this page;
* only allow scripts bundled with, or generated by, the software.
* Site-wide styles are controlled by a config setting, since they can be
@@ -1585,6 +1654,7 @@ class OutputPage extends ContextSource {
* @param string $text
* @param bool $linestart Is this the start of a line?
* @param bool $interface Is this text in the user interface language?
+ * @throws MWException
*/
public function addWikiText( $text, $linestart = true, $interface = true ) {
$title = $this->getTitle(); // Work around E_STRICT
@@ -1642,8 +1712,6 @@ class OutputPage extends ContextSource {
) {
global $wgParser;
- wfProfileIn( __METHOD__ );
-
$popts = $this->parserOptions();
$oldTidy = $popts->setTidy( $tidy );
$popts->setInterfaceMessage( (bool)$interface );
@@ -1657,7 +1725,6 @@ class OutputPage extends ContextSource {
$this->addParserOutput( $parserOutput );
- wfProfileOut( __METHOD__ );
}
/**
@@ -1681,6 +1748,7 @@ class OutputPage extends ContextSource {
public function addParserOutputMetadata( $parserOutput ) {
$this->mLanguageLinks += $parserOutput->getLanguageLinks();
$this->addCategoryLinks( $parserOutput->getCategories() );
+ $this->setIndicators( $parserOutput->getIndicators() );
$this->mNewSectionLink = $parserOutput->getNewSection();
$this->mHideNewSectionLink = $parserOutput->getHideNewSection();
@@ -1723,8 +1791,8 @@ class OutputPage extends ContextSource {
// Link flags are ignored for now, but may in the future be
// used to mark individual language links.
$linkFlags = array();
- wfRunHooks( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) );
- wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
+ Hooks::run( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) );
+ Hooks::run( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
}
/**
@@ -1753,7 +1821,7 @@ class OutputPage extends ContextSource {
*/
public function addParserOutputText( $parserOutput ) {
$text = $parserOutput->getText();
- wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
+ Hooks::run( 'OutputPageBeforeHTML', array( &$this, &$text ) );
$this->addHTML( $text );
}
@@ -1878,7 +1946,7 @@ class OutputPage extends ContextSource {
),
$config->get( 'CacheVaryCookies' )
);
- wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) );
+ Hooks::run( 'GetCacheVaryCookies', array( $this, &$cookies ) );
}
return $cookies;
}
@@ -2125,14 +2193,10 @@ class OutputPage extends ContextSource {
* the object, let's actually output it:
*/
public function output() {
- global $wgLanguageCode;
-
if ( $this->mDoNothing ) {
return;
}
- wfProfileIn( __METHOD__ );
-
$response = $this->getRequest()->response();
$config = $this->getConfig();
@@ -2143,7 +2207,7 @@ class OutputPage extends ContextSource {
$redirect = $this->mRedirect;
$code = $this->mRedirectCode;
- if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
+ if ( Hooks::run( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
if ( $code == '301' || $code == '303' ) {
if ( !$config->get( 'DebugRedirects' ) ) {
$message = HttpStatus::getMessage( $code );
@@ -2167,7 +2231,6 @@ class OutputPage extends ContextSource {
}
}
- wfProfileOut( __METHOD__ );
return;
} elseif ( $this->mStatusCode ) {
$message = HttpStatus::getMessage( $this->mStatusCode );
@@ -2180,7 +2243,7 @@ class OutputPage extends ContextSource {
ob_start();
$response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
- $response->header( 'Content-language: ' . $wgLanguageCode );
+ $response->header( 'Content-language: ' . $config->get( 'LanguageCode' ) );
// Avoid Internet Explorer "compatibility view" in IE 8-10, so that
// jQuery etc. can work correctly.
@@ -2220,21 +2283,18 @@ class OutputPage extends ContextSource {
// Hook that allows last minute changes to the output page, e.g.
// adding of CSS or Javascript by extensions.
- wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
+ Hooks::run( 'BeforePageDisplay', array( &$this, &$sk ) );
- wfProfileIn( 'Output-skin' );
$sk->outputPage();
- wfProfileOut( 'Output-skin' );
}
// This hook allows last minute changes to final overall output by modifying output buffer
- wfRunHooks( 'AfterFinalPageOutput', array( $this ) );
+ Hooks::run( 'AfterFinalPageOutput', array( $this ) );
$this->sendCacheControl();
ob_end_flush();
- wfProfileOut( __METHOD__ );
}
/**
@@ -2454,90 +2514,32 @@ class OutputPage extends ContextSource {
}
/**
- * Display a page stating that the Wiki is in read-only mode,
- * and optionally show the source of the page that the user
- * was trying to edit. Should only be called (for this
- * purpose) after wfReadOnly() has returned true.
+ * Display a page stating that the Wiki is in read-only mode.
+ * Should only be called after wfReadOnly() has returned true.
*
- * For historical reasons, this function is _also_ used to
- * show the error message when a user tries to edit a page
- * they are not allowed to edit. (Unless it's because they're
- * blocked, then we show blockedPage() instead.) In this
- * case, the second parameter should be set to true and a list
- * of reasons supplied as the third parameter.
+ * Historically, this function was used to show the source of the page that the user
+ * was trying to edit and _also_ permissions error messages. The relevant code was
+ * moved into EditPage in 1.19 (r102024 / d83c2a431c2a) and removed here in 1.25.
*
- * @todo Needs to be split into multiple functions.
- *
- * @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
+ * @deprecated since 1.25; throw the exception directly
* @throws ReadOnlyError
*/
- public function readOnlyPage( $source = null, $protected = false,
- array $reasons = array(), $action = null
- ) {
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
-
- // If no reason is given, just supply a default "I can't let you do
- // that, Dave" message. Should only occur if called by legacy code.
- if ( $protected && empty( $reasons ) ) {
- $reasons[] = array( 'badaccess-group0' );
- }
-
- if ( !empty( $reasons ) ) {
- // Permissions error
- if ( $source ) {
- $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
- $this->addBacklinkSubtitle( $this->getTitle() );
- } else {
- $this->setPageTitle( $this->msg( 'badaccess' ) );
- }
- $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
- } else {
- // Wiki is read only
- throw new ReadOnlyError;
- }
-
- // Show source, if supplied
- if ( is_string( $source ) ) {
- $this->addWikiMsg( 'viewsourcetext' );
-
- $pageLang = $this->getTitle()->getPageLanguage();
- $params = array(
- 'id' => 'wpTextbox1',
- 'name' => 'wpTextbox1',
- 'cols' => $this->getUser()->getOption( 'cols' ),
- 'rows' => $this->getUser()->getOption( 'rows' ),
- 'readonly' => 'readonly',
- 'lang' => $pageLang->getHtmlCode(),
- 'dir' => $pageLang->getDir(),
- );
- $this->addHTML( Html::element( 'textarea', $params, $source ) );
-
- // Show templates used by this article
- $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
- $this->addHTML( "<div class='templatesUsed'>
-$templates
-</div>
-" );
+ public function readOnlyPage() {
+ if ( func_num_args() > 0 ) {
+ throw new MWException( __METHOD__ . ' no longer accepts arguments since 1.25.' );
}
- # If the title doesn't exist, it's fairly pointless to print a return
- # link to it. After all, you just tried editing it and couldn't, so
- # what's there to do there?
- if ( $this->getTitle()->exists() ) {
- $this->returnToMain( null, $this->getTitle() );
- }
+ throw new ReadOnlyError;
}
/**
* Turn off regular page output and return an error response
* for when rate limiting has triggered.
+ *
+ * @deprecated since 1.25; throw the exception directly
*/
public function rateLimited() {
+ wfDeprecated( __METHOD__, '1.25' );
throw new ThrottledError;
}
@@ -2713,7 +2715,7 @@ $templates
// Allow skins and extensions to add body attributes they need
$sk->addToBodyAttributes( $this, $bodyAttrs );
- wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
+ Hooks::run( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
$ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
@@ -2824,7 +2826,6 @@ $templates
);
$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;
@@ -2905,11 +2906,11 @@ $templates
);
} 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.
+ if ( !$context->getRaw() && !$isRaw ) {
+ // Wrap only=script / only=combined 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 ) )
@@ -3108,7 +3109,7 @@ $templates
// 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();' );
+ $html = Html::inlineScript( 'if(window.jQuery)jQuery.ready();' );
if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
$html .= $this->getScriptsForBottomQueue( false );
@@ -3223,6 +3224,7 @@ $templates
'wgMonthNames' => $lang->getMonthNamesArray(),
'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
+ 'wgRelevantArticleId' => $relevantTitle->getArticleId(),
);
if ( $user->isLoggedIn() ) {
@@ -3263,7 +3265,7 @@ $templates
// Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
// page-dependant but site-wide (without state).
// Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
- wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
+ Hooks::run( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
// Merge in variables from addJsConfigVars last
return array_merge( $vars, $this->getJsConfigVars() );
@@ -3313,6 +3315,13 @@ $templates
'content' => "MediaWiki $wgVersion",
) );
+ if ( $config->get( 'ReferrerPolicy' ) !== false ) {
+ $tags['meta-referrer'] = Html::element( 'meta', array(
+ 'name' => 'referrer',
+ 'content' => $config->get( 'ReferrerPolicy' )
+ ) );
+ }
+
$p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
if ( $p !== 'index,follow' ) {
// http://www.robotstxt.org/wc/meta-user.html
@@ -3615,7 +3624,9 @@ $templates
$moduleStyles[] = 'user.groups';
// Per-user custom styles
- if ( $this->getConfig()->get( 'AllowUserCss' ) && $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
+ 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,
@@ -3813,12 +3824,13 @@ $templates
* This function takes a number of message/argument specifications, wraps them in
* some overall structure, and then parses the result and adds it to the output.
*
- * In the $wrap, $1 is replaced with the first message, $2 with the second, and so
- * on. The subsequent arguments may either be strings, in which case they are the
- * message names, or arrays, in which case the first element is the message name,
- * and subsequent elements are the parameters to that message.
+ * In the $wrap, $1 is replaced with the first message, $2 with the second,
+ * and so on. The subsequent arguments may be either
+ * 1) strings, in which case they are message names, or
+ * 2) arrays, in which case, within each array, the first element is the message
+ * name, and subsequent elements are the parameters to that message.
*
- * Don't use this for messages that are not in users interface language.
+ * Don't use this for messages that are not in the user's interface language.
*
* For example:
*
@@ -3829,7 +3841,7 @@ $templates
* $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.
+ * The newline after the opening div is needed in some wikitext. See bug 19226.
*
* @param string $wrap
*/
@@ -3904,4 +3916,16 @@ $templates
public function sectionEditLinksEnabled() {
return $this->mEnableSectionEditLinks;
}
+
+ /**
+ * Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with
+ * MediaWiki and this OutputPage instance.
+ *
+ * @since 1.25
+ */
+ public function enableOOUI() {
+ OOUI\Theme::setSingleton( new OOUI\MediaWikiTheme() );
+ OOUI\Element::setDefaultDir( $this->getLanguage()->getDir() );
+ $this->addModuleStyles( 'oojs-ui.styles' );
+ }
}
diff --git a/includes/PHPVersionCheck.php b/includes/PHPVersionCheck.php
new file mode 100644
index 00000000..eee9aa9c
--- /dev/null
+++ b/includes/PHPVersionCheck.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * Check PHP Version, as well as for composer dependencies in entry points,
+ * and display something vaguely comprehensible in the event of a totally
+ * unrecoverable error.
+ *
+ * This 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
+ */
+
+/**
+ * Check php version and that external dependencies are installed, and
+ * display an informative error if either condition is not satisfied.
+ */
+function wfEntryPointCheck( $entryPoint ) {
+ if ( !function_exists( 'version_compare' )
+ || version_compare( PHP_VERSION, '5.3.3' ) < 0
+ || !file_exists( dirname( __FILE__ ) . '/../vendor/autoload.php' )
+ ) {
+ wfPHPVersionError( $entryPoint );
+ }
+}
+
+/**
+ * Display something vaguely comprehensible in the event of a totally unrecoverable error.
+ * Does not assume access to *anything*; no globals, no autoloader, no database, no localisation.
+ * Safe for PHP4 (and putting this here means that WebStart.php and GlobalSettings.php
+ * no longer need to be).
+ *
+ * Calling this function kills execution immediately.
+ *
+ * @param string $type Which entry point we are protecting. One of:
+ * - 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.25';
+ $minimumVersionPHP = '5.3.3';
+
+ $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. Installing some "
+ . " external dependencies (e.g. via composer) is also required.";
+
+ if ( $type == 'cli' ) {
+ $finalOutput = "Error: You are missing some external dependencies or are using on older PHP version. \n"
+ . "MediaWiki $mwVersion needs PHP $minimumVersionPHP or higher.\n\n"
+ . "Check if you have a newer php executable with a different name, such as php5.\n\n"
+ . "MediaWiki now also has some external dependencies that need to be installed\n"
+ . "via composer or from a separate git repo. Please see\n"
+ . "https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries\n"
+ . "for help on installing the required components.";
+ } 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( '//', '/', $dirname . '/' ) .
+ 'resources/assets/mediawiki.png'
+ );
+
+ header( "$protocol 500 MediaWiki configuration Error" );
+ header( 'Content-type: text/html; charset=UTF-8' );
+ // Don't cache error pages! They cause no end of trouble...
+ header( 'Cache-control: none' );
+ header( 'Pragma: no-cache' );
+
+ $finalOutput = <<<HTML
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+ <head>
+ <meta charset="UTF-8" />
+ <title>MediaWiki {$mwVersion}</title>
+ <style media='screen'>
+ body {
+ color: #000;
+ background-color: #fff;
+ font-family: sans-serif;
+ padding: 2em;
+ text-align: center;
+ }
+ p, img, h1, h2 {
+ text-align: left;
+ margin: 0.5em 0 1em;
+ }
+ h1 {
+ font-size: 120%;
+ }
+ h2 {
+ font-size: 110%;
+ }
+ </style>
+ </head>
+ <body>
+ <img src="{$encLogo}" alt='The MediaWiki logo' />
+ <h1>MediaWiki {$mwVersion} internal error</h1>
+ <div class='error'>
+ <p>
+ {$message}
+ </p>
+ <h2>Supported PHP versions</h2>
+ <p>
+ Please consider <a href="http://www.php.net/downloads.php">upgrading your copy of PHP</a>.
+ PHP versions less than 5.3.0 are no longer supported by the PHP Group and will not receive
+ security or bugfix updates.
+ </p>
+ <p>
+ If for some reason you are unable to upgrade your PHP version, you will need to
+ <a href="https://www.mediawiki.org/wiki/Download">download</a> an older version
+ of MediaWiki from our website. See our
+ <a href="https://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a>
+ for details of which versions are compatible with prior versions of PHP.
+ </p>
+ <h2>External dependencies</h2>
+ <p>
+ MediaWiki now also has some external dependencies that need to be installed via
+ composer or from a separate git repo. Please see
+ <a href="https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a>
+ for help on installing the required components.
+ </p>
+ </div>
+ </body>
+</html>
+HTML;
+ // Handle everything that's not index.php
+ } else {
+ // So nothing thinks this is JS or CSS
+ $finalOutput = ( $type == 'load.php' ) ? "/* $message */" : $message;
+ header( "$protocol 500 MediaWiki configuration Error" );
+ }
+ echo "$finalOutput\n";
+ die( 1 );
+}
diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php
index f481650c..007ea894 100644
--- a/includes/PHPVersionError.php
+++ b/includes/PHPVersionError.php
@@ -1,6 +1,7 @@
<?php
/**
- * Display something vaguely comprehensible in the event of a totally unrecoverable error.
+ * Backwards compatibility. The PHP version error function is now
+ * included in PHPVersionCheck.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
@@ -17,110 +18,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @deprecated 1.25
* @file
*/
-
-/**
- * Display something vaguely comprehensible in the event of a totally unrecoverable error.
- * Does not assume access to *anything*; no globals, no autoloader, no database, no localisation.
- * Safe for PHP4 (and putting this here means that WebStart.php and GlobalSettings.php
- * no longer need to be).
- *
- * Calling this function kills execution immediately.
- *
- * @param string $type Which entry point we are protecting. One of:
- * - 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.24';
- $minimumVersionPHP = '5.3.2';
-
- $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.";
-
- 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' || $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( '//', '/', $dirname . '/' ) .
- 'resources/assets/mediawiki.png'
- );
-
- header( "$protocol 500 MediaWiki configuration Error" );
- header( 'Content-type: text/html; charset=UTF-8' );
- // Don't cache error pages! They cause no end of trouble...
- header( 'Cache-control: none' );
- header( 'Pragma: no-cache' );
-
- $finalOutput = <<<HTML
-<!DOCTYPE html>
-<html lang="en" dir="ltr">
- <head>
- <meta charset="UTF-8" />
- <title>MediaWiki {$mwVersion}</title>
- <style media='screen'>
- body {
- color: #000;
- background-color: #fff;
- font-family: sans-serif;
- padding: 2em;
- text-align: center;
- }
- p, img, h1 {
- text-align: left;
- margin: 0.5em 0;
- }
- h1 {
- font-size: 120%;
- }
- </style>
- </head>
- <body>
- <img src="{$encLogo}" alt='The MediaWiki logo' />
- <h1>MediaWiki {$mwVersion} internal error</h1>
- <div class='error'>
- <p>
- {$message}
- </p>
- <p>
- Please consider <a href="http://www.php.net/downloads.php">upgrading your copy of PHP</a>.
- PHP versions less than 5.3.0 are no longer supported by the PHP Group and will not receive
- security or bugfix updates.
- </p>
- <p>
- If for some reason you are unable to upgrade your PHP version, you will need to
- <a href="https://www.mediawiki.org/wiki/Download">download</a> an older version
- of MediaWiki from our website. See our
- <a href="https://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a>
- for details of which versions are compatible with prior versions of PHP.
- </p>
- </div>
- </body>
-</html>
-HTML;
- // Handle everything that's not index.php
- } else {
- // So nothing thinks this is JS or CSS
- $finalOutput = ( $type == 'load.php' ) ? "/* $message */" : $message;
- header( "$protocol 500 MediaWiki configuration Error" );
- }
- echo "$finalOutput\n";
- die( 1 );
-}
+require_once dirname( __FILE__ ) . '/PHPVersionCheck.php';
diff --git a/includes/Preferences.php b/includes/Preferences.php
index 84cf5af0..a5239331 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -96,7 +96,7 @@ class Preferences {
self::searchPreferences( $user, $context, $defaultPreferences );
self::miscPreferences( $user, $context, $defaultPreferences );
- wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) );
+ Hooks::run( 'GetPreferences', array( $user, &$defaultPreferences ) );
self::loadPreferenceValues( $user, $context, $defaultPreferences );
self::$defaultPreferences = $defaultPreferences;
@@ -130,8 +130,7 @@ class Preferences {
if ( $disable && !in_array( $name, self::$saveBlacklist ) ) {
$info['disabled'] = 'disabled';
}
- $field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation
- $field->mParent = $dummyForm;
+ $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation
$defaultOptions = User::getDefaultOptions();
$globalDefault = isset( $defaultOptions[$name] )
? $defaultOptions[$name]
@@ -244,10 +243,9 @@ class Preferences {
'type' => 'info',
'label' => $context->msg( 'prefs-memberingroups' )->numParams(
count( $userGroups ) )->params( $userName )->parse(),
- 'default' => $context->msg( 'prefs-memberingroups-type',
- $lang->commaList( $userGroups ),
- $lang->commaList( $userMembers )
- )->plain(),
+ 'default' => $context->msg( 'prefs-memberingroups-type' )
+ ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
+ ->escaped(),
'raw' => true,
'section' => 'personal/info',
);
@@ -339,11 +337,11 @@ class Preferences {
'type' => 'radio',
'section' => 'personal/i18n',
'options' => array(
- $context->msg( 'parentheses',
- $context->msg( 'gender-unknown' )->text()
- )->text() => 'unknown',
- $context->msg( 'gender-female' )->text() => 'female',
- $context->msg( 'gender-male' )->text() => 'male',
+ $context->msg( 'parentheses' )
+ ->params( $context->msg( 'gender-unknown' )->plain() )
+ ->escaped() => 'unknown',
+ $context->msg( 'gender-female' )->escaped() => 'female',
+ $context->msg( 'gender-male' )->escaped() => 'male',
),
'label-message' => 'yourgender',
'help-message' => 'prefs-help-gender',
@@ -451,8 +449,8 @@ class Preferences {
array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
$emailAddress .= $emailAddress == '' ? $link : (
- $context->msg( 'word-separator' )->plain()
- . $context->msg( 'parentheses' )->rawParams( $link )->plain()
+ $context->msg( 'word-separator' )->escaped()
+ . $context->msg( 'parentheses' )->rawParams( $link )->escaped()
);
}
@@ -870,7 +868,7 @@ class Preferences {
'min' => 1,
'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
'help' => $context->msg( 'recentchangesdays-max' )->numParams(
- ceil( $rcMaxAge / ( 3600 * 24 ) ) )->text()
+ ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped()
);
$defaultPreferences['rclimit'] = array(
'type' => 'int',
@@ -895,6 +893,9 @@ class Preferences {
'section' => 'rc/advancedrc',
'label-message' => 'tog-hidepatrolled',
);
+ }
+
+ if ( $user->useNPPatrol() ) {
$defaultPreferences['newpageshidepatrolled'] = array(
'type' => 'toggle',
'section' => 'rc/advancedrc',
@@ -921,13 +922,37 @@ class Preferences {
$watchlistdaysMax = ceil( $config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
## Watchlist #####################################
+ if ( $user->isAllowed( 'editmywatchlist' ) ) {
+ $editWatchlistLinks = array();
+ $editWatchlistModes = array(
+ 'edit' => array( 'EditWatchlist', false ),
+ 'raw' => array( 'EditWatchlist', 'raw' ),
+ 'clear' => array( 'EditWatchlist', 'clear' ),
+ );
+ foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) {
+ // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
+ $editWatchlistLinks[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( $mode[0], $mode[1] ),
+ $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse()
+ );
+ }
+
+ $defaultPreferences['editwatchlist'] = array(
+ 'type' => 'info',
+ 'raw' => true,
+ 'default' => $context->getLanguage()->pipeList( $editWatchlistLinks ),
+ 'label-message' => 'prefs-editwatchlist-label',
+ 'section' => 'watchlist/editwatchlist',
+ );
+ }
+
$defaultPreferences['watchlistdays'] = array(
'type' => 'float',
'min' => 0,
'max' => $watchlistdaysMax,
'section' => 'watchlist/displaywatchlist',
'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams(
- $watchlistdaysMax )->text(),
+ $watchlistdaysMax )->escaped(),
'label-message' => 'prefs-watchlist-days',
);
$defaultPreferences['wllimit'] = array(
@@ -969,7 +994,7 @@ class Preferences {
'label-message' => 'tog-watchlisthideliu',
);
- if ( $context->getConfig()->get( 'UseRCPatrol' ) ) {
+ if ( $user->useRCPatrol() ) {
$defaultPreferences['watchlisthidepatrolled'] = array(
'type' => 'toggle',
'section' => 'watchlist/advancedwatchlist',
@@ -1047,7 +1072,7 @@ class Preferences {
$ret = array();
$mptitle = Title::newMainPage();
- $previewtext = $context->msg( 'skin-preview' )->text();
+ $previewtext = $context->msg( 'skin-preview' )->escaped();
# Only show skins that aren't disabled in $wgSkipSkins
$validSkinNames = Skin::getAllowedSkins();
@@ -1072,7 +1097,7 @@ class Preferences {
$linkTools = array();
# Mark the default skin
- if ( $skinkey == $defaultSkin ) {
+ if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
$linkTools[] = $context->msg( 'default' )->escaped();
$foundDefault = true;
}
@@ -1092,10 +1117,9 @@ class Preferences {
$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' )
+ ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
+ ->escaped();
$ret[$display] = $skinkey;
}
@@ -1433,7 +1457,7 @@ class Preferences {
$user->setOption( $key, $value );
}
- wfRunHooks( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) );
+ Hooks::run( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) );
$user->saveSettings();
}
@@ -1466,28 +1490,6 @@ class Preferences {
return Status::newGood();
}
-
- /**
- * Try to set a user's email address.
- * This does *not* try to validate the address.
- * Caller is responsible for checking $wgAuth and 'editmyprivateinfo'
- * right.
- *
- * @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)
- */
- public static function trySetUserEmail( User $user, $newaddr ) {
- wfDeprecated( __METHOD__, '1.20' );
-
- $result = $user->setEmailWithConfirmation( $newaddr );
- if ( $result->isGood() ) {
- return array( true, $result->value );
- } else {
- return array( $result, 'mailerror' );
- }
- }
}
/** Some tweaks to allow js prefs to work */
@@ -1539,12 +1541,8 @@ class PreferencesForm extends HTMLForm {
* @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 '';
@@ -1556,7 +1554,7 @@ class PreferencesForm extends HTMLForm {
$t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
$html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped(),
- $attrs );
+ Html::buttonAttributes( $attrs, array( 'mw-ui-quiet' ) ) );
$html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
}
@@ -1601,7 +1599,7 @@ class PreferencesForm extends HTMLForm {
*/
function getLegend( $key ) {
$legend = parent::getLegend( $key );
- wfRunHooks( 'PreferencesGetLegend', array( $this, $key, &$legend ) );
+ Hooks::run( 'PreferencesGetLegend', array( $this, $key, &$legend ) );
return $legend;
}
}
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php
index 718750f5..55a4f49b 100644
--- a/includes/PrefixSearch.php
+++ b/includes/PrefixSearch.php
@@ -34,11 +34,12 @@ abstract class PrefixSearch {
* @param string $search
* @param int $limit
* @param array $namespaces Used if query is not explicitly prefixed
+ * @param int $offset How many results to offset from the beginning
* @return array Array of strings
*/
- public static function titleSearch( $search, $limit, $namespaces = array() ) {
+ public static function titleSearch( $search, $limit, $namespaces = array(), $offset = 0 ) {
$prefixSearch = new StringPrefixSearch;
- return $prefixSearch->search( $search, $limit, $namespaces );
+ return $prefixSearch->search( $search, $limit, $namespaces, $offset );
}
/**
@@ -47,9 +48,10 @@ abstract class PrefixSearch {
* @param string $search
* @param int $limit
* @param array $namespaces Used if query is not explicitly prefixed
+ * @param int $offset How many results to offset from the beginning
* @return array Array of strings or Title objects
*/
- public function search( $search, $limit, $namespaces = array() ) {
+ public function search( $search, $limit, $namespaces = array(), $offset = 0 ) {
$search = trim( $search );
if ( $search == '' ) {
return array(); // Return empty result
@@ -60,11 +62,12 @@ abstract class PrefixSearch {
$title = Title::newFromText( $search );
if ( $title && !$title->isExternal() ) {
$ns = array( $title->getNamespace() );
+ $search = $title->getText();
if ( $ns[0] == NS_MAIN ) {
$ns = $namespaces; // no explicit prefix, use default namespaces
+ Hooks::run( 'PrefixSearchExtractNamespace', array( &$ns, &$search ) );
}
- return $this->searchBackend(
- $ns, $title->getText(), $limit );
+ return $this->searchBackend( $ns, $search, $limit, $offset );
}
// Is this a namespace prefix?
@@ -75,9 +78,11 @@ abstract class PrefixSearch {
{
$namespaces = array( $title->getNamespace() );
$search = '';
+ } else {
+ Hooks::run( 'PrefixSearchExtractNamespace', array( &$namespaces, &$search ) );
}
- return $this->searchBackend( $namespaces, $search, $limit );
+ return $this->searchBackend( $namespaces, $search, $limit, $offset );
}
/**
@@ -85,12 +90,12 @@ abstract class PrefixSearch {
* @param string $search
* @param int $limit
* @param array $namespaces
+ * @param int $offset How many results to offset from the beginning
*
* @return array
*/
- public function searchWithVariants( $search, $limit, array $namespaces ) {
- wfProfileIn( __METHOD__ );
- $searches = $this->search( $search, $limit, $namespaces );
+ public function searchWithVariants( $search, $limit, array $namespaces, $offset = 0 ) {
+ $searches = $this->search( $search, $limit, $namespaces, $offset );
// if the content language has variants, try to retrieve fallback results
$fallbackLimit = $limit - count( $searches );
@@ -110,7 +115,6 @@ abstract class PrefixSearch {
}
}
}
- wfProfileOut( __METHOD__ );
return $searches;
}
@@ -138,22 +142,128 @@ abstract class PrefixSearch {
* @param array $namespaces
* @param string $search
* @param int $limit
+ * @param int $offset How many results to offset from the beginning
* @return array Array of strings
*/
- protected function searchBackend( $namespaces, $search, $limit ) {
+ protected function searchBackend( $namespaces, $search, $limit, $offset ) {
if ( count( $namespaces ) == 1 ) {
$ns = $namespaces[0];
if ( $ns == NS_MEDIA ) {
$namespaces = array( NS_FILE );
} elseif ( $ns == NS_SPECIAL ) {
- return $this->titles( $this->specialSearch( $search, $limit ) );
+ return $this->titles( $this->specialSearch( $search, $limit, $offset ) );
}
}
$srchres = array();
- if ( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) {
- return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit ) );
+ if ( Hooks::run(
+ 'PrefixSearchBackend',
+ array( $namespaces, $search, $limit, &$srchres, $offset )
+ ) ) {
+ return $this->titles( $this->defaultSearchBackend( $namespaces, $search, $limit, $offset ) );
}
- return $this->strings( $srchres );
+ return $this->strings( $this->handleResultFromHook( $srchres, $namespaces, $search, $limit ) );
+ }
+
+ /**
+ * Default search backend does proper prefix searching, but custom backends
+ * may sort based on other algorythms that may cause the exact title match
+ * to not be in the results or be lower down the list.
+ * @param array $srchres results from the hook
+ * @return array munged results from the hook
+ */
+ private function handleResultFromHook( $srchres, $namespaces, $search, $limit ) {
+ // Pick namespace (based on PrefixSearch::defaultSearchBackend)
+ $ns = in_array( NS_MAIN, $namespaces ) ? NS_MAIN : $namespaces[0];
+ $t = Title::newFromText( $search, $ns );
+ if ( !$t || !$t->exists() ) {
+ // No exact match so just return the search results
+ return $srchres;
+ }
+ $string = $t->getPrefixedText();
+ $key = array_search( $string, $srchres );
+ if ( $key !== false ) {
+ // Exact match was in the results so just move it to the front
+ return $this->pullFront( $key, $srchres );
+ }
+ // Exact match not in the search results so check for some redirect handling cases
+ if ( $t->isRedirect() ) {
+ $target = $this->getRedirectTarget( $t );
+ $key = array_search( $target, $srchres );
+ if ( $key !== false ) {
+ // Exact match is a redirect to one of the returned matches so pull the
+ // returned match to the front. This might look odd but the alternative
+ // is to put the redirect in front and drop the match. The name of the
+ // found match is often more descriptive/better formed than the name of
+ // the redirect AND by definition they share a prefix. Hopefully this
+ // choice is less confusing and more helpful. But it might not be. But
+ // it is the choice we're going with for now.
+ return $this->pullFront( $key, $srchres );
+ }
+ $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
+ if ( isset( $redirectTargetsToRedirect[$target] ) ) {
+ // The exact match and something in the results list are both redirects
+ // to the same thing! In this case we'll pull the returned match to the
+ // top following the same logic above. Again, it might not be a perfect
+ // choice but it'll do.
+ return $this->pullFront( $redirectTargetsToRedirect[$target], $srchres );
+ }
+ } else {
+ $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
+ if ( isset( $redirectTargetsToRedirect[$string] ) ) {
+ // The exact match is the target of a redirect already in the results list so remove
+ // the redirect from the results list and push the exact match to the front
+ array_splice( $srchres, $redirectTargetsToRedirect[$string], 1 );
+ array_unshift( $srchres, $string );
+ return $srchres;
+ }
+ }
+
+ // Exact match is totally unique from the other results so just add it to the front
+ array_unshift( $srchres, $string );
+ // And roll one off the end if the results are too long
+ if ( count( $srchres ) > $limit ) {
+ array_pop( $srchres );
+ }
+ return $srchres;
+ }
+
+ /**
+ * @param Array(string) $titles as strings
+ * @return Array(string => int) redirect target prefixedText to index of title in titles
+ * that is a redirect to it.
+ */
+ private function redirectTargetsToRedirect( $titles ) {
+ $result = array();
+ foreach ( $titles as $key => $titleText ) {
+ $title = Title::newFromText( $titleText );
+ if ( !$title || !$title->isRedirect() ) {
+ continue;
+ }
+ $target = $this->getRedirectTarget( $title );
+ if ( !$target ) {
+ continue;
+ }
+ $result[$target] = $key;
+ }
+ return $result;
+ }
+
+ /**
+ * @param int $key key to pull to the front
+ * @return array $array with the item at $key pulled to the front
+ */
+ private function pullFront( $key, $array ) {
+ $cut = array_splice( $array, $key, 1 );
+ array_unshift( $array, $cut[0] );
+ return $array;
+ }
+
+ private function getRedirectTarget( $title ) {
+ $page = WikiPage::factory( $title );
+ if ( !$page->exists() ) {
+ return null;
+ }
+ return $page->getRedirectTarget()->getPrefixedText();
}
/**
@@ -161,9 +271,10 @@ abstract class PrefixSearch {
*
* @param string $search Term
* @param int $limit Max number of items to return
+ * @param int $offset Number of items to offset
* @return array
*/
- protected function specialSearch( $search, $limit ) {
+ protected function specialSearch( $search, $limit, $offset ) {
global $wgContLang;
$searchParts = explode( '/', $search, 2 );
@@ -179,7 +290,7 @@ abstract class PrefixSearch {
}
$special = SpecialPageFactory::getPage( $specialTitle->getText() );
if ( $special ) {
- $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit );
+ $subpages = $special->prefixSearchSubpages( $subpageSearch, $limit, $offset );
return array_map( function ( $sub ) use ( $specialTitle ) {
return $specialTitle->getSubpage( $sub );
}, $subpages );
@@ -195,7 +306,7 @@ abstract class PrefixSearch {
// Unlike SpecialPage itself, we want the canonical forms of both
// canonical and alias title forms...
$keys = array();
- foreach ( SpecialPageFactory::getNames() as $page ) {
+ foreach ( SpecialPageFactory::getNames() as $page ) {
$keys[$wgContLang->caseFold( $page )] = $page;
}
@@ -211,12 +322,17 @@ abstract class PrefixSearch {
ksort( $keys );
$srchres = array();
+ $skipped = 0;
foreach ( $keys as $pageKey => $page ) {
if ( $searchKey === '' || strpos( $pageKey, $searchKey ) === 0 ) {
// 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'
+ if ( $offset > 0 && $skipped < $offset ) {
+ $skipped++;
+ continue;
+ }
$srchres[] = Title::makeTitleSafe( NS_SPECIAL, $page );
}
@@ -237,9 +353,10 @@ abstract class PrefixSearch {
* @param array $namespaces Namespaces to search in
* @param string $search Term
* @param int $limit Max number of items to return
+ * @param int $offset Number of items to skip
* @return array Array of Title objects
*/
- protected function defaultSearchBackend( $namespaces, $search, $limit ) {
+ protected function defaultSearchBackend( $namespaces, $search, $limit, $offset ) {
$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
@@ -255,7 +372,11 @@ abstract class PrefixSearch {
'page_title ' . $dbr->buildLike( $prefix, $dbr->anyString() )
),
__METHOD__,
- array( 'LIMIT' => $limit, 'ORDER BY' => 'page_title' )
+ array(
+ 'LIMIT' => $limit,
+ 'ORDER BY' => 'page_title',
+ 'OFFSET' => $offset
+ )
);
$srchres = array();
foreach ( $res as $row ) {
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index 7bad8b57..1219da51 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -156,7 +156,7 @@ class ProtectionForm {
} else {
$value = $this->mExpirySelection[$action];
}
- if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
+ if ( wfIsInfinity( $value ) ) {
$time = wfGetDB( DB_SLAVE )->getInfinity();
} else {
$unix = strtotime( $value );
@@ -209,11 +209,11 @@ class ProtectionForm {
if ( $this->mTitle->getRestrictionTypes() === array() ) {
// No restriction types available for the current title
// this might happen if an extension alters the available types
- $out->setPageTitle( wfMessage(
+ $out->setPageTitle( $this->mContext->msg(
'protect-norestrictiontypes-title',
$this->mTitle->getPrefixedText()
) );
- $out->addWikiText( wfMessage( 'protect-norestrictiontypes-text' )->text() );
+ $out->addWikiText( $this->mContext->msg( 'protect-norestrictiontypes-text' )->plain() );
// Show the log in case protection was possible once
$this->showLogExtract( $out );
@@ -240,12 +240,12 @@ class ProtectionForm {
# the protection settings at this time
if ( $this->disabled ) {
$out->setPageTitle(
- wfMessage( 'protect-title-notallowed',
+ $this->mContext->msg( 'protect-title-notallowed',
$this->mTitle->getPrefixedText() )
);
$out->addWikiText( $out->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
} else {
- $out->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
+ $out->setPageTitle( $this->mContext->msg( 'protect-title', $this->mTitle->getPrefixedText() ) );
$out->addWikiMsg( 'protect-text',
wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
}
@@ -279,7 +279,7 @@ class ProtectionForm {
$reasonstr = $this->mReasonSelection;
if ( $reasonstr != 'other' && $this->mReason != '' ) {
// Entry from drop down menu + additional comment
- $reasonstr .= wfMessage( 'colon-separator' )->text() . $this->mReason;
+ $reasonstr .= $this->mContext->msg( 'colon-separator' )->text() . $this->mReason;
} elseif ( $reasonstr == 'other' ) {
$reasonstr = $this->mReason;
}
@@ -321,7 +321,7 @@ class ProtectionForm {
* you can also return an array of message name and its parameters
*/
$errorMsg = '';
- if ( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg, $reasonstr ) ) ) {
+ if ( !Hooks::run( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg, $reasonstr ) ) ) {
if ( $errorMsg == '' ) {
$errorMsg = array( 'hookaborted' );
}
@@ -342,10 +342,11 @@ class ProtectionForm {
* @return string HTML form
*/
function buildForm() {
- $user = $this->mContext->getUser();
- $output = $this->mContext->getOutput();
- $lang = $this->mContext->getLanguage();
- $cascadingRestrictionLevels = $this->mContext->getConfig()->get( 'CascadingRestrictionLevels' );
+ $context = $this->mContext;
+ $user = $context->getUser();
+ $output = $context->getOutput();
+ $lang = $context->getLanguage();
+ $cascadingRestrictionLevels = $context->getConfig()->get( 'CascadingRestrictionLevels' );
$out = '';
if ( !$this->disabled ) {
$output->addModules( 'mediawiki.legacy.protect' );
@@ -356,7 +357,7 @@ class ProtectionForm {
}
$out .= Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMessage( 'protect-legend' )->text() ) .
+ Xml::element( 'legend', null, $context->msg( 'protect-legend' )->text() ) .
Xml::openElement( 'table', array( 'id' => 'mwProtectSet' ) ) .
Xml::openElement( 'tbody' );
@@ -367,7 +368,7 @@ class ProtectionForm {
foreach ( $this->mRestrictions as $action => $selected ) {
// Messages:
// restriction-edit, restriction-move, restriction-create, restriction-upload
- $msg = wfMessage( 'restriction-' . $action );
+ $msg = $context->msg( 'restriction-' . $action );
$out .= "<tr><td>" .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, $msg->exists() ? $msg->text() : $action ) .
@@ -375,23 +376,23 @@ class ProtectionForm {
"<tr><td>" . $this->buildSelector( $action, $selected ) . "</td></tr><tr><td>";
$mProtectexpiry = Xml::label(
- wfMessage( 'protectexpiry' )->text(),
+ $context->msg( 'protectexpiry' )->text(),
"mwProtectExpirySelection-$action"
);
$mProtectother = Xml::label(
- wfMessage( 'protect-othertime' )->text(),
+ $context->msg( 'protect-othertime' )->text(),
"mwProtect-$action-expires"
);
$expiryFormOptions = '';
if ( $this->mExistingExpiry[$action] ) {
if ( $this->mExistingExpiry[$action] == 'infinity' ) {
- $existingExpiryMessage = wfMessage( 'protect-existing-expiry-infinity' );
+ $existingExpiryMessage = $context->msg( '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 );
+ $timestamp = $lang->userTimeAndDate( $this->mExistingExpiry[$action], $user );
+ $d = $lang->userDate( $this->mExistingExpiry[$action], $user );
+ $t = $lang->userTime( $this->mExistingExpiry[$action], $user );
+ $existingExpiryMessage = $context->msg( 'protect-existing-expiry', $timestamp, $d, $t );
}
$expiryFormOptions .=
Xml::option(
@@ -402,7 +403,7 @@ class ProtectionForm {
}
$expiryFormOptions .= Xml::option(
- wfMessage( 'protect-othertime-op' )->text(),
+ $context->msg( 'protect-othertime-op' )->text(),
"othertime"
) . "\n";
foreach ( explode( ',', $scExpiryOptions ) as $option ) {
@@ -411,11 +412,9 @@ class ProtectionForm {
} else {
list( $show, $value ) = explode( ":", $option );
}
- $show = htmlspecialchars( $show );
- $value = htmlspecialchars( $value );
$expiryFormOptions .= Xml::option(
$show,
- $value,
+ htmlspecialchars( $value ),
$this->mExpirySelection[$action] === $value
) . "\n";
}
@@ -452,7 +451,7 @@ class ProtectionForm {
"</td></tr>";
}
# Give extensions a chance to add items to the form
- wfRunHooks( 'ProtectionForm::buildForm', array( $this->mArticle, &$out ) );
+ Hooks::run( 'ProtectionForm::buildForm', array( $this->mArticle, &$out ) );
$out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
@@ -464,7 +463,7 @@ class ProtectionForm {
<td></td>
<td class="mw-input">' .
Xml::checkLabel(
- wfMessage( 'protect-cascade' )->text(),
+ $context->msg( 'protect-cascade' )->text(),
'mwProtect-cascade',
'mwProtect-cascade',
$this->mCascade, $this->disabledAttrib
@@ -477,12 +476,12 @@ class ProtectionForm {
# Add manual and custom reason field/selects as well as submit
if ( !$this->disabled ) {
$mProtectreasonother = Xml::label(
- wfMessage( 'protectcomment' )->text(),
+ $context->msg( 'protectcomment' )->text(),
'wpProtectReasonSelection'
);
$mProtectreason = Xml::label(
- wfMessage( 'protect-otherreason' )->text(),
+ $context->msg( 'protect-otherreason' )->text(),
'mwProtect-reason'
);
@@ -521,7 +520,7 @@ class ProtectionForm {
<tr>
<td></td>
<td class='mw-input'>" .
- Xml::checkLabel( wfMessage( 'watchthis' )->text(),
+ Xml::checkLabel( $context->msg( 'watchthis' )->text(),
'mwProtectWatch', 'mwProtectWatch',
$user->isWatched( $this->mTitle ) || $user->getOption( 'watchdefault' ) ) .
"</td>
@@ -532,7 +531,7 @@ class ProtectionForm {
<td></td>
<td class='mw-submit'>" .
Xml::submitButton(
- wfMessage( 'confirm' )->text(),
+ $context->msg( 'confirm' )->text(),
array( 'id' => 'mw-Protect-submit' )
) .
"</td>
@@ -545,7 +544,7 @@ class ProtectionForm {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' );
$link = Linker::link(
$title,
- wfMessage( 'protect-edit-reasonlist' )->escaped(),
+ $context->msg( 'protect-edit-reasonlist' )->escaped(),
array(),
array( 'action' => 'edit' )
);
@@ -600,14 +599,14 @@ class ProtectionForm {
*/
private function getOptionLabel( $permission ) {
if ( $permission == '' ) {
- return wfMessage( 'protect-default' )->text();
+ return $this->mContext->msg( 'protect-default' )->text();
} else {
// Messages: protect-level-autoconfirmed, protect-level-sysop
- $msg = wfMessage( "protect-level-{$permission}" );
+ $msg = $this->mContext->msg( "protect-level-{$permission}" );
if ( $msg->exists() ) {
return $msg->text();
}
- return wfMessage( 'protect-fallback', $permission )->text();
+ return $this->mContext->msg( 'protect-fallback', $permission )->text();
}
}
@@ -623,6 +622,6 @@ class ProtectionForm {
$out->addHTML( Xml::element( 'h2', null, $protectLogPage->getName()->text() ) );
LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
# Let extensions add other relevant log extracts
- wfRunHooks( 'ProtectionForm::showLogExtract', array( $this->mArticle, $out ) );
+ Hooks::run( 'ProtectionForm::showLogExtract', array( $this->mArticle, $out ) );
}
}
diff --git a/includes/Revision.php b/includes/Revision.php
index 28a825d0..3ba6157c 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -121,7 +121,7 @@ class Revision implements IDBAccessObject {
if ( $id ) {
// Use the specified ID
$conds['rev_id'] = $id;
- return self::newFromConds( $conds, (int)$flags );
+ return self::newFromConds( $conds, $flags );
} else {
// Use a join to get the latest revision
$conds[] = 'rev_id=page_latest';
@@ -148,11 +148,13 @@ class Revision implements IDBAccessObject {
$conds = array( 'page_id' => $pageId );
if ( $revId ) {
$conds['rev_id'] = $revId;
+ return self::newFromConds( $conds, $flags );
} else {
// Use a join to get the latest revision
$conds[] = 'rev_id = page_latest';
+ $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+ return self::loadFromConds( $db, $conds, $flags );
}
- return self::newFromConds( $conds, (int)$flags );
}
/**
@@ -295,7 +297,10 @@ class Revision implements IDBAccessObject {
}
/**
- * Given a set of conditions, fetch a revision.
+ * Given a set of conditions, fetch a revision
+ *
+ * This method is used then a revision ID is qualified and
+ * will incorporate some basic slave/master fallback logic
*
* @param array $conditions
* @param int $flags (optional)
@@ -303,16 +308,24 @@ class Revision implements IDBAccessObject {
*/
private static function newFromConds( $conditions, $flags = 0 ) {
$db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
+
$rev = self::loadFromConds( $db, $conditions, $flags );
- if ( $rev === null && wfGetLB()->getServerCount() > 1 ) {
- if ( !( $flags & self::READ_LATEST ) ) {
- $dbw = wfGetDB( DB_MASTER );
- $rev = self::loadFromConds( $dbw, $conditions, $flags );
- }
+ // Make sure new pending/committed revision are visibile later on
+ // within web requests to certain avoid bugs like T93866 and T94407.
+ if ( !$rev
+ && !( $flags & self::READ_LATEST )
+ && wfGetLB()->getServerCount() > 1
+ && wfGetLB()->hasOrMadeRecentMasterChanges()
+ ) {
+ $flags = self::READ_LATEST;
+ $db = wfGetDB( DB_MASTER );
+ $rev = self::loadFromConds( $db, $conditions, $flags );
}
+
if ( $rev ) {
$rev->mQueryFlags = $flags;
}
+
return $rev;
}
@@ -515,7 +528,6 @@ class Revision implements IDBAccessObject {
if ( !$revIds ) {
return $revLens; // empty
}
- wfProfileIn( __METHOD__ );
$res = $db->select( 'revision',
array( 'rev_id', 'rev_len' ),
array( 'rev_id' => $revIds ),
@@ -523,7 +535,6 @@ class Revision implements IDBAccessObject {
foreach ( $res as $row ) {
$revLens[$row->rev_id] = $row->rev_len;
}
- wfProfileOut( __METHOD__ );
return $revLens;
}
@@ -678,13 +689,8 @@ class Revision implements IDBAccessObject {
$this->mCurrent = false;
// If we still have no length, see it we have the text to figure it out
- if ( !$this->mSize ) {
- if ( $this->mContent !== null ) {
- $this->mSize = $this->mContent->getSize();
- } else {
- #NOTE: this should never happen if we have either text or content object!
- $this->mSize = null;
- }
+ if ( !$this->mSize && $this->mContent !== null ) {
+ $this->mSize = $this->mContent->getSize();
}
// Same for sha1
@@ -833,9 +839,11 @@ class Revision implements IDBAccessObject {
* Fetch revision's user id without regard for the current user's permissions
*
* @return string
+ * @deprecated since 1.25, use getUser( Revision::RAW )
*/
public function getRawUser() {
- return $this->mUser;
+ wfDeprecated( __METHOD__, '1.25' );
+ return $this->getUser( self::RAW );
}
/**
@@ -857,7 +865,15 @@ class Revision implements IDBAccessObject {
} elseif ( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
return '';
} else {
- return $this->getRawUserText();
+ if ( $this->mUserText === null ) {
+ $this->mUserText = User::whoIs( $this->mUser ); // load on demand
+ if ( $this->mUserText === false ) {
+ # This shouldn't happen, but it can if the wiki was recovered
+ # via importing revs and there is no user table entry yet.
+ $this->mUserText = $this->mOrigUserText;
+ }
+ }
+ return $this->mUserText;
}
}
@@ -865,17 +881,11 @@ class Revision implements IDBAccessObject {
* Fetch revision's username without regard for view restrictions
*
* @return string
+ * @deprecated since 1.25, use getUserText( Revision::RAW )
*/
public function getRawUserText() {
- if ( $this->mUserText === null ) {
- $this->mUserText = User::whoIs( $this->mUser ); // load on demand
- if ( $this->mUserText === false ) {
- # This shouldn't happen, but it can if the wiki was recovered
- # via importing revs and there is no user table entry yet.
- $this->mUserText = $this->mOrigUserText;
- }
- }
- return $this->mUserText;
+ wfDeprecated( __METHOD__, '1.25' );
+ return $this->getUserText( self::RAW );
}
/**
@@ -905,9 +915,11 @@ class Revision implements IDBAccessObject {
* Fetch revision comment without regard for the current user's permissions
*
* @return string
+ * @deprecated since 1.25, use getComment( Revision::RAW )
*/
public function getRawComment() {
- return $this->mComment;
+ wfDeprecated( __METHOD__, '1.25' );
+ return $this->getComment( self::RAW );
}
/**
@@ -943,7 +955,7 @@ class Revision implements IDBAccessObject {
$dbr = wfGetDB( DB_SLAVE );
return RecentChange::newFromConds(
array(
- 'rc_user_text' => $this->getRawUserText(),
+ 'rc_user_text' => $this->getUserText( Revision::RAW ),
'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
'rc_this_oldid' => $this->getId()
),
@@ -1065,8 +1077,6 @@ class Revision implements IDBAccessObject {
$format = $this->getContentFormat();
$this->mContent = $handler->unserializeContent( $this->mText, $format );
- } else {
- $this->mContent = false; // negative caching!
}
}
@@ -1220,7 +1230,6 @@ class Revision implements IDBAccessObject {
* @return string Text the text requested or false on failure
*/
public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) {
- wfProfileIn( __METHOD__ );
# Get data
$textField = $prefix . 'text';
@@ -1235,7 +1244,6 @@ class Revision implements IDBAccessObject {
if ( isset( $row->$textField ) ) {
$text = $row->$textField;
} else {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -1244,7 +1252,6 @@ class Revision implements IDBAccessObject {
$url = $text;
$parts = explode( '://', $url, 2 );
if ( count( $parts ) == 1 || $parts[1] == '' ) {
- wfProfileOut( __METHOD__ );
return false;
}
$text = ExternalStore::fetchFromURL( $url, array( 'wiki' => $wiki ) );
@@ -1254,7 +1261,6 @@ class Revision implements IDBAccessObject {
if ( $text !== false ) {
$text = self::decompressRevisionText( $text, $flags );
}
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -1338,8 +1344,6 @@ class Revision implements IDBAccessObject {
public function insertOn( $dbw ) {
global $wgDefaultExternalStore, $wgContentHandlerUseDB;
- wfProfileIn( __METHOD__ );
-
$this->checkContentModel();
$data = $this->mText;
@@ -1350,7 +1354,6 @@ class Revision implements IDBAccessObject {
// Store and get the URL
$data = ExternalStore::insertToDefault( $data );
if ( !$data ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "Unable to store text to external storage" );
}
if ( $flags ) {
@@ -1410,7 +1413,6 @@ class Revision implements IDBAccessObject {
$title = $this->getTitle();
if ( $title === null ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "Insufficient information to determine the title of the "
. "revision's page!" );
}
@@ -1426,9 +1428,16 @@ class Revision implements IDBAccessObject {
$this->mId = $rev_id !== null ? $rev_id : $dbw->insertId();
- wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
+ // Assertion to try to catch T92046
+ if ( (int)$this->mId === 0 ) {
+ throw new UnexpectedValueException(
+ 'After insert, Revision mId is ' . var_export( $this->mId, 1 ) . ': ' .
+ var_export( $row, 1 )
+ );
+ }
+
+ Hooks::run( 'RevisionInsertComplete', array( &$this, $data, $flags ) );
- wfProfileOut( __METHOD__ );
return $this->mId;
}
@@ -1497,17 +1506,15 @@ class Revision implements IDBAccessObject {
* @return string|bool The revision's text, or false on failure
*/
protected function loadText() {
- wfProfileIn( __METHOD__ );
-
// Caching may be beneficial for massive use of external storage
global $wgRevisionCacheExpiry, $wgMemc;
+
$textId = $this->getTextId();
$key = wfMemcKey( 'revisiontext', 'textid', $textId );
if ( $wgRevisionCacheExpiry ) {
$text = $wgMemc->get( $key );
if ( is_string( $text ) ) {
wfDebug( __METHOD__ . ": got id $textId from cache\n" );
- wfProfileOut( __METHOD__ );
return $text;
}
}
@@ -1555,8 +1562,6 @@ class Revision implements IDBAccessObject {
$wgMemc->set( $key, $text, $wgRevisionCacheExpiry );
}
- wfProfileOut( __METHOD__ );
-
return $text;
}
@@ -1576,9 +1581,7 @@ class Revision implements IDBAccessObject {
* @return Revision|null Revision or null on error
*/
public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
- global $wgContentHandlerUseDB;
-
- wfProfileIn( __METHOD__ );
+ global $wgContentHandlerUseDB, $wgContLang;
$fields = array( 'page_latest', 'page_namespace', 'page_title',
'rev_text_id', 'rev_len', 'rev_sha1' );
@@ -1603,6 +1606,9 @@ class Revision implements IDBAccessObject {
$user = $wgUser;
}
+ // Truncate for whole multibyte characters
+ $summary = $wgContLang->truncate( $summary, 255 );
+
$row = array(
'page' => $pageId,
'user_text' => $user->getName(),
@@ -1626,7 +1632,6 @@ class Revision implements IDBAccessObject {
$revision = null;
}
- wfProfileOut( __METHOD__ );
return $revision;
}
@@ -1697,23 +1702,21 @@ class Revision implements IDBAccessObject {
*
* @param Title $title
* @param int $id
- * @return string
+ * @return string|bool False if not found
*/
- static function getTimestampFromId( $title, $id ) {
- $dbr = wfGetDB( DB_SLAVE );
+ static function getTimestampFromId( $title, $id, $flags = 0 ) {
+ $db = ( $flags & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
// Casting fix for databases that can't take '' for rev_id
if ( $id == '' ) {
$id = 0;
}
$conds = array( 'rev_id' => $id );
$conds['rev_page'] = $title->getArticleID();
- $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
- if ( $timestamp === false && wfGetLB()->getServerCount() > 1 ) {
- # Not in slave, try master
- $dbw = wfGetDB( DB_MASTER );
- $timestamp = $dbw->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
- }
- return wfTimestamp( TS_MW, $timestamp );
+ $timestamp = $db->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
+
+ return ( $timestamp !== false ) ? wfTimestamp( TS_MW, $timestamp ) : false;
}
/**
diff --git a/includes/RevisionList.php b/includes/RevisionList.php
index d10b5412..6844dadc 100644
--- a/includes/RevisionList.php
+++ b/includes/RevisionList.php
@@ -317,7 +317,7 @@ class RevisionItem extends RevisionItemBase {
}
public function getAuthorNameField() {
- return 'user_name'; // see Revision::selectUserFields()
+ return 'rev_user_text';
}
public function canView() {
@@ -334,15 +334,18 @@ class RevisionItem extends RevisionItemBase {
/**
* Get the HTML link to the revision text.
- * Overridden by RevDelArchiveItem.
+ * @todo Essentially a copy of RevDelRevisionItem::getRevisionLink. That class
+ * should inherit from this one, and implement an appropriate interface instead
+ * of extending RevDelItem
* @return string
*/
protected function getRevisionLink() {
$date = $this->list->getLanguage()->timeanddate( $this->revision->getTimestamp(), true );
+
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $date;
}
- return Linker::link(
+ return Linker::linkKnown(
$this->list->title,
$date,
array(),
@@ -355,30 +358,34 @@ class RevisionItem extends RevisionItemBase {
/**
* Get the HTML link to the diff.
- * Overridden by RevDelArchiveItem
+ * @todo Essentially a copy of RevDelRevisionItem::getDiffLink. That class
+ * should inherit from this one, and implement an appropriate interface instead
+ * of extending RevDelItem
* @return string
*/
protected function getDiffLink() {
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $this->context->msg( 'diff' )->escaped();
} else {
- return Linker::link(
+ return Linker::linkKnown(
$this->list->title,
- $this->context->msg( 'diff' )->escaped(),
+ $this->list->msg( 'diff' )->escaped(),
array(),
array(
'diff' => $this->revision->getId(),
'oldid' => 'prev',
'unhide' => 1
- ),
- array(
- 'known',
- 'noclasses'
)
);
}
}
+ /**
+ * @todo Essentially a copy of RevDelRevisionItem::getHTML. That class
+ * should inherit from this one, and implement an appropriate interface instead
+ * of extending RevDelItem
+ * @return string
+ */
public function getHTML() {
$difflink = $this->context->msg( 'parentheses' )
->rawParams( $this->getDiffLink() )->escaped();
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index bca2f67e..96193a74 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -40,6 +40,12 @@ class Sanitizer {
|(&)/x';
/**
+ * Acceptable tag name charset from HTML5 parsing spec
+ * http://www.w3.org/TR/html5/syntax.html#tag-open-state
+ */
+ const ELEMENT_BITS_REGEX = '!^(/?)([A-Za-z][^\t\n\v />\0]*+)([^>]*?)(/?>)([^<]*)$!';
+
+ /**
* Blacklist for evil uris like javascript:
* WARNING: DO NOT use this in any place that actually requires blacklisting
* for security reasons. There are NUMEROUS[1] ways to bypass blacklisting, the
@@ -355,7 +361,6 @@ class Sanitizer {
/**
* Cleans up HTML, removes dangerous tags and attributes, and
* removes HTML comments
- * @private
* @param string $text
* @param callable $processCallback Callback to do any variable or parameter
* replacements in HTML attribute values
@@ -364,7 +369,7 @@ class Sanitizer {
* @param array $removetags For any tags (default or extra) to exclude
* @return string
*/
- static function removeHTMLtags( $text, $processCallback = null,
+ public static function removeHTMLtags( $text, $processCallback = null,
$args = array(), $extratags = array(), $removetags = array()
) {
global $wgUseTidy, $wgAllowMicrodataAttributes, $wgAllowImageTag;
@@ -372,8 +377,6 @@ class Sanitizer {
static $htmlpairsStatic, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags,
$htmllist, $listtags, $htmlsingleallowed, $htmlelementsStatic, $staticInitialised;
- wfProfileIn( __METHOD__ );
-
// Base our staticInitialised variable off of the global config state so that if the globals
// are changed (like in the screwed up test system) we will re-initialise the settings.
$globalContext = implode( '-', compact( 'wgAllowMicrodataAttributes', 'wgAllowImageTag' ) );
@@ -447,7 +450,7 @@ class Sanitizer {
# $params: String between element name and >
# $brace: Ending '>' or '/>'
# $rest: Everything until the next element of $bits
- if ( preg_match( '!^(/?)([^\\s/>]+)([^>]*?)(/{0,1}>)([^<]*)$!', $x, $regs ) ) {
+ if ( preg_match( self::ELEMENT_BITS_REGEX, $x, $regs ) ) {
list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
} else {
$slash = $t = $params = $brace = $rest = null;
@@ -510,15 +513,12 @@ class Sanitizer {
$newparams = '';
} else {
# Keep track for later
- if ( isset( $tabletags[$t] ) &&
- !in_array( 'table', $tagstack ) ) {
+ if ( isset( $tabletags[$t] ) && !in_array( 'table', $tagstack ) ) {
$badtag = true;
- } elseif ( in_array( $t, $tagstack ) &&
- !isset( $htmlnest[$t] ) ) {
+ } elseif ( in_array( $t, $tagstack ) && !isset( $htmlnest[$t] ) ) {
$badtag = true;
# Is it a self closed htmlpair ? (bug 5487)
- } elseif ( $brace == '/>' &&
- isset( $htmlpairs[$t] ) ) {
+ } elseif ( $brace == '/>' && isset( $htmlpairs[$t] ) ) {
$badtag = true;
} elseif ( isset( $htmlsingleonly[$t] ) ) {
# Hack to force empty tag for unclosable elements
@@ -530,8 +530,7 @@ class Sanitizer {
# the tag stack so that we can match end tags
# instead of marking them as bad.
array_push( $tagstack, $t );
- } elseif ( isset( $tabletags[$t] )
- && in_array( $t, $tagstack ) ) {
+ } elseif ( isset( $tabletags[$t] ) && in_array( $t, $tagstack ) ) {
// New table tag but forgot to close the previous one
$text .= "</$t>";
} else {
@@ -574,37 +573,30 @@ class Sanitizer {
} else {
# this might be possible using tidy itself
foreach ( $bits as $x ) {
- preg_match(
- '/^(\\/?)(\\w+)([^>]*?)(\\/{0,1}>)([^<]*)$/',
- $x,
- $regs
- );
-
- wfSuppressWarnings();
- list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
- wfRestoreWarnings();
+ if ( preg_match( self::ELEMENT_BITS_REGEX, $x, $regs ) ) {
+ list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs;
- $badtag = false;
- if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
- if ( is_callable( $processCallback ) ) {
- call_user_func_array( $processCallback, array( &$params, $args ) );
- }
+ $badtag = false;
+ if ( isset( $htmlelements[$t = strtolower( $t )] ) ) {
+ if ( is_callable( $processCallback ) ) {
+ call_user_func_array( $processCallback, array( &$params, $args ) );
+ }
- if ( !Sanitizer::validateTag( $params, $t ) ) {
- $badtag = true;
- }
+ if ( !Sanitizer::validateTag( $params, $t ) ) {
+ $badtag = true;
+ }
- $newparams = Sanitizer::fixTagAttributes( $params, $t );
- if ( !$badtag ) {
- $rest = str_replace( '>', '&gt;', $rest );
- $text .= "<$slash$t$newparams$brace$rest";
- continue;
+ $newparams = Sanitizer::fixTagAttributes( $params, $t );
+ if ( !$badtag ) {
+ $rest = str_replace( '>', '&gt;', $rest );
+ $text .= "<$slash$t$newparams$brace$rest";
+ continue;
+ }
}
}
$text .= '&lt;' . str_replace( '>', '&gt;', $x );
}
}
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -614,12 +606,10 @@ class Sanitizer {
* and followed by a newline (ignoring spaces), trim leading and
* trailing spaces and one of the newlines.
*
- * @private
* @param string $text
* @return string
*/
- static function removeHTMLcomments( $text ) {
- wfProfileIn( __METHOD__ );
+ public static function removeHTMLcomments( $text ) {
while ( ( $start = strpos( $text, '<!--' ) ) !== false ) {
$end = strpos( $text, '-->', $start + 4 );
if ( $end === false ) {
@@ -650,7 +640,6 @@ class Sanitizer {
$text = substr_replace( $text, '', $start, $end - $start );
}
}
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -874,7 +863,7 @@ class Sanitizer {
$value = preg_replace_callback(
'/[!-[]-z]/u', // U+FF01 to U+FF5A, excluding U+FF3C (bug 58088)
function ( $matches ) {
- $cp = utf8ToCodepoint( $matches[0] );
+ $cp = UtfNormal\Utils::utf8ToCodepoint( $matches[0] );
if ( $cp === false ) {
return '';
}
@@ -980,7 +969,7 @@ class Sanitizer {
// Line continuation
return '';
} elseif ( $matches[2] !== '' ) {
- $char = codepointToUtf8( hexdec( $matches[2] ) );
+ $char = UtfNormal\Utils::codepointToUtf8( hexdec( $matches[2] ) );
} elseif ( $matches[3] !== '' ) {
$char = $matches[3];
} else {
@@ -1120,14 +1109,14 @@ class Sanitizer {
$id = preg_replace( '/[ \t\n\r\f_\'"&#%]+/', '_', $id );
$id = trim( $id, '_' );
if ( $id === '' ) {
- # Must have been all whitespace to start with.
+ // Must have been all whitespace to start with.
return '_';
} else {
return $id;
}
}
- # HTML4-style escaping
+ // HTML4-style escaping
static $replace = array(
'%3A' => ':',
'%' => '.'
@@ -1136,8 +1125,7 @@ class Sanitizer {
$id = urlencode( strtr( $id, ' ', '_' ) );
$id = str_replace( array_keys( $replace ), array_values( $replace ), $id );
- if ( !preg_match( '/^[a-zA-Z]/', $id )
- && !in_array( 'noninitial', $options ) ) {
+ if ( !preg_match( '/^[a-zA-Z]/', $id ) && !in_array( 'noninitial', $options ) ) {
// Initial character must be a letter!
$id = "x$id";
}
@@ -1273,24 +1261,6 @@ class Sanitizer {
}
/**
- * Normalize whitespace and character references in an XML source-
- * encoded text for an attribute value.
- *
- * See http://www.w3.org/TR/REC-xml/#AVNormalize for background,
- * but note that we're not returning the value, but are returning
- * XML source fragments that will be slapped into output.
- *
- * @param string $text
- * @return string
- * @todo Remove, unused?
- */
- private static function normalizeAttributeValue( $text ) {
- return str_replace( '"', '&quot;',
- self::normalizeWhitespace(
- Sanitizer::normalizeCharReferences( $text ) ) );
- }
-
- /**
* @param string $text
* @return string
*/
@@ -1368,8 +1338,7 @@ class Sanitizer {
static function normalizeEntity( $name ) {
if ( isset( self::$htmlEntityAliases[$name] ) ) {
return '&' . self::$htmlEntityAliases[$name] . ';';
- } elseif ( in_array( $name,
- array( 'lt', 'gt', 'amp', 'quot' ) ) ) {
+ } elseif ( in_array( $name, array( 'lt', 'gt', 'amp', 'quot' ) ) ) {
return "&$name;";
} elseif ( isset( self::$htmlEntities[$name] ) ) {
return '&#' . self::$htmlEntities[$name] . ';';
@@ -1481,9 +1450,9 @@ class Sanitizer {
*/
static function decodeChar( $codepoint ) {
if ( Sanitizer::validateCodepoint( $codepoint ) ) {
- return codepointToUtf8( $codepoint );
+ return UtfNormal\Utils::codepointToUtf8( $codepoint );
} else {
- return UTF8_REPLACEMENT;
+ return UtfNormal\Constants::UTF8_REPLACEMENT;
}
}
@@ -1500,7 +1469,7 @@ class Sanitizer {
$name = self::$htmlEntityAliases[$name];
}
if ( isset( self::$htmlEntities[$name] ) ) {
- return codepointToUtf8( self::$htmlEntities[$name] );
+ return UtfNormal\Utils::codepointToUtf8( self::$htmlEntities[$name] );
} else {
return "&$name;";
}
@@ -1861,7 +1830,7 @@ class Sanitizer {
*/
public static function validateEmail( $addr ) {
$result = null;
- if ( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
+ if ( !Hooks::run( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
return $result;
}
diff --git a/includes/Setup.php b/includes/Setup.php
index 7a89c7a3..dd8fbf8a 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -33,8 +33,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
$fname = 'Setup.php';
-wfProfileIn( $fname );
-wfProfileIn( $fname . '-defaults' );
+$ps_setup = Profiler::instance()->scopedProfileIn( $fname );
+
+// If any extensions are still queued, force load them
+ExtensionRegistry::getInstance()->loadFromQueue();
// Check to see if we are at the file scope
if ( !isset( $wgVersion ) ) {
@@ -43,6 +45,7 @@ if ( !isset( $wgVersion ) ) {
}
// Set various default paths sensibly...
+$ps_default = Profiler::instance()->scopedProfileIn( $fname . '-defaults' );
if ( $wgScript === false ) {
$wgScript = "$wgScriptPath/index$wgScriptExtension";
@@ -71,9 +74,6 @@ if ( $wgStylePath === false ) {
if ( $wgLocalStylePath === false ) {
$wgLocalStylePath = "$wgScriptPath/skins";
}
-if ( $wgStyleDirectory === false ) {
- $wgStyleDirectory = "$IP/skins";
-}
if ( $wgExtensionAssetsPath === false ) {
$wgExtensionAssetsPath = "$wgScriptPath/extensions";
}
@@ -137,6 +137,9 @@ if ( isset( $wgFooterIcons['poweredby'] )
) {
$wgFooterIcons['poweredby']['mediawiki']['src'] =
"$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png";
+ $wgFooterIcons['poweredby']['mediawiki']['srcset'] =
+ "$wgResourceBasePath/resources/assets/poweredby_mediawiki_132x47.png 1.5x, " .
+ "$wgResourceBasePath/resources/assets/poweredby_mediawiki_176x62.png 2x";
}
/**
@@ -289,8 +292,13 @@ call_user_func( function () use ( $wgValidSkinNames ) {
$factory->register( 'fallback', 'Fallback', function () {
return new SkinFallback;
} );
+ // Register a hidden skin for api output
+ $factory->register( 'apioutput', 'ApiOutput', function () {
+ return new SkinApi;
+ } );
} );
$wgSkipSkins[] = 'fallback';
+$wgSkipSkins[] = 'apioutput';
if ( $wgLocalInterwiki ) {
array_unshift( $wgLocalInterwikis, $wgLocalInterwiki );
@@ -301,6 +309,11 @@ if ( $wgSharedPrefix === false ) {
$wgSharedPrefix = $wgDBprefix;
}
+// Set default shared schema
+if ( $wgSharedSchema === false ) {
+ $wgSharedSchema = $wgDBmwschema;
+}
+
if ( !$wgCookiePrefix ) {
if ( $wgSharedDB && $wgSharedPrefix && in_array( 'user', $wgSharedTables ) ) {
$wgCookiePrefix = $wgSharedDB . '_' . $wgSharedPrefix;
@@ -346,10 +359,15 @@ if ( $wgMetaNamespace === false ) {
$wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
}
-// Default value is either the suhosin limit or -1 for unlimited
+// Default value is 2000 or the suhosin limit if it is between 1 and 2000
if ( $wgResourceLoaderMaxQueryLength === false ) {
- $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
- $wgResourceLoaderMaxQueryLength = $maxValueLength > 0 ? $maxValueLength : -1;
+ $suhosinMaxValueLength = (int) ini_get( 'suhosin.get.max_value_length' );
+ if ( $suhosinMaxValueLength > 0 && $suhosinMaxValueLength < 2000 ) {
+ $wgResourceLoaderMaxQueryLength = $suhosinMaxValueLength;
+ } else {
+ $wgResourceLoaderMaxQueryLength = 2000;
+ }
+ unset($suhosinMaxValueLength);
}
/**
@@ -448,32 +466,28 @@ if ( $wgProfileOnly ) {
$wgDebugLogFile = '';
}
-wfProfileOut( $fname . '-defaults' );
+Profiler::instance()->scopedProfileOut( $ps_default );
// Disable MWDebug for command line mode, this prevents MWDebug from eating up
// all the memory from logging SQL queries on maintenance scripts
global $wgCommandLineMode;
if ( $wgDebugToolbar && !$wgCommandLineMode ) {
- wfProfileIn( $fname . '-debugtoolbar' );
MWDebug::init();
- wfProfileOut( $fname . '-debugtoolbar' );
}
if ( !class_exists( 'AutoLoader' ) ) {
require_once "$IP/includes/AutoLoader.php";
}
-wfProfileIn( $fname . '-exception' );
MWExceptionHandler::installHandler();
-wfProfileOut( $fname . '-exception' );
-wfProfileIn( $fname . '-includes' );
-require_once "$IP/includes/normal/UtfNormalUtil.php";
-require_once "$IP/includes/GlobalFunctions.php";
-require_once "$IP/includes/normal/UtfNormalDefines.php";
-wfProfileOut( $fname . '-includes' );
+require_once "$IP/includes/libs/normal/UtfNormalUtil.php";
-wfProfileIn( $fname . '-defaults2' );
+$ps_default2 = Profiler::instance()->scopedProfileIn( $fname . '-defaults2' );
+
+if ( $wgScriptExtension !== '.php' || defined( 'MW_ENTRY_PHP5' ) ) {
+ wfWarn( 'Script extensions other than ".php" are deprecated.' );
+}
if ( $wgCanonicalServer === false ) {
$wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
@@ -504,16 +518,22 @@ if ( $wgSecureLogin && substr( $wgServer, 0, 2 ) !== '//' ) {
. 'HTTP or HTTPS. Disabling secure login.' );
}
-// Now that GlobalFunctions is loaded, set defaults that depend
-// on it.
+// Now that GlobalFunctions is loaded, set defaults that depend on it.
if ( $wgTmpDirectory === false ) {
- wfProfileIn( $fname . '-tempDir' );
+ $ps_tmpdir = Profiler::instance()->scopedProfileIn( $fname . '-tempDir' );
$wgTmpDirectory = wfTempDir();
- wfProfileOut( $fname . '-tempDir' );
+ Profiler::instance()->scopedProfileOut( $ps_tmpdir );
+}
+
+// We don't use counters anymore. Left here for extensions still
+// expecting this to exist. Should be removed sometime 1.26 or later.
+if ( !isset( $wgDisableCounters ) ) {
+ $wgDisableCounters = true;
}
-wfProfileOut( $fname . '-defaults2' );
-wfProfileIn( $fname . '-misc1' );
+Profiler::instance()->scopedProfileOut( $ps_default2 );
+
+$ps_misc = Profiler::instance()->scopedProfileIn( $fname . '-misc1' );
// Raise the memory limit if it's too low
wfMemoryLimit();
@@ -555,24 +575,23 @@ if ( $wgCommandLineMode ) {
wfDebug( $debug );
}
-wfProfileOut( $fname . '-misc1' );
-wfProfileIn( $fname . '-memcached' );
+Profiler::instance()->scopedProfileOut( $ps_misc );
+$ps_memcached = Profiler::instance()->scopedProfileIn( $fname . '-memcached' );
$wgMemc = wfGetMainCache();
$messageMemc = wfGetMessageCacheStorage();
$parserMemc = wfGetParserCacheStorage();
-$wgLangConvMemc = wfGetLangConverterCacheStorage();
wfDebugLog( 'caches', 'main: ' . get_class( $wgMemc ) .
', message: ' . get_class( $messageMemc ) .
', parser: ' . get_class( $parserMemc ) );
-wfProfileOut( $fname . '-memcached' );
+Profiler::instance()->scopedProfileOut( $ps_memcached );
// Most of the config is out, some might want to run hooks here.
-wfRunHooks( 'SetupAfterCache' );
+Hooks::run( 'SetupAfterCache' );
-wfProfileIn( $fname . '-session' );
+$ps_session = Profiler::instance()->scopedProfileIn( $fname . '-session' );
if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
// If session.auto_start is there, we can't touch session name
@@ -585,8 +604,8 @@ if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
}
}
-wfProfileOut( $fname . '-session' );
-wfProfileIn( $fname . '-globals' );
+Profiler::instance()->scopedProfileOut( $ps_session );
+$ps_globals = Profiler::instance()->scopedProfileIn( $fname . '-globals' );
/**
* @var Language $wgContLang
@@ -620,7 +639,7 @@ $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParser
if ( !is_object( $wgAuth ) ) {
$wgAuth = new AuthPlugin;
- wfRunHooks( 'AuthPluginSetup', array( &$wgAuth ) );
+ Hooks::run( 'AuthPluginSetup', array( &$wgAuth ) );
}
/**
@@ -634,8 +653,8 @@ $wgTitle = null;
*/
$wgDeferredUpdateList = array();
-wfProfileOut( $fname . '-globals' );
-wfProfileIn( $fname . '-extensions' );
+Profiler::instance()->scopedProfileOut( $ps_globals );
+$ps_extensions = Profiler::instance()->scopedProfileIn( $fname . '-extensions' );
// Extension setup functions for extensions other than skins
// Entries should be added to this variable during the inclusion
@@ -655,13 +674,14 @@ foreach ( $wgExtensionFunctions as $func ) {
$profName = $fname . '-extensions-' . strval( $func );
}
- wfProfileIn( $profName );
+ $ps_ext_func = Profiler::instance()->scopedProfileIn( $profName );
call_user_func( $func );
- wfProfileOut( $profName );
+ Profiler::instance()->scopedProfileOut( $ps_ext_func );
}
wfDebug( "Fully initialised\n" );
$wgFullyInitialised = true;
-wfProfileOut( $fname . '-extensions' );
-wfProfileOut( $fname );
+Profiler::instance()->scopedProfileOut( $ps_extensions );
+Profiler::instance()->scopedProfileOut( $ps_setup );
+
diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php
index 8c1f26b8..c3b1a6ac 100644
--- a/includes/SiteConfiguration.php
+++ b/includes/SiteConfiguration.php
@@ -133,6 +133,8 @@ class SiteConfiguration {
/**
* Array of domains that are local and can be handled by the same server
+ *
+ * @deprecated since 1.25; use $wgLocalVirtualHosts instead.
*/
public $localVHosts = array();
@@ -565,6 +567,8 @@ class SiteConfiguration {
/**
* Returns true if the given vhost is handled locally.
+ *
+ * @deprecated since 1.25; check if the host is in $wgLocalVirtualHosts instead.
* @param string $vhost
* @return bool
*/
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index 3dc17933..15c18f35 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -102,7 +102,6 @@ class SiteStats {
static function doLoad( $db ) {
return $db->selectRow( 'site_stats', array(
'ss_row_id',
- 'ss_total_views',
'ss_total_edits',
'ss_good_articles',
'ss_total_pages',
@@ -113,11 +112,16 @@ class SiteStats {
}
/**
+ * Return the total number of page views. Except we don't track those anymore.
+ * Stop calling this function, it will be removed some time in the future. It's
+ * kept here simply to prevent fatal errors.
+ *
+ * @deprecated since 1.25
* @return int
*/
static function views() {
- self::load();
- return self::$row->ss_total_views;
+ wfDeprecated( __METHOD__, '1.25' );
+ return 0;
}
/**
@@ -217,7 +221,6 @@ class SiteStats {
* @return int
*/
static function pagesInNs( $ns ) {
- wfProfileIn( __METHOD__ );
if ( !isset( self::$pageCount[$ns] ) ) {
$dbr = wfGetDB( DB_SLAVE );
self::$pageCount[$ns] = (int)$dbr->selectField(
@@ -227,7 +230,6 @@ class SiteStats {
__METHOD__
);
}
- wfProfileOut( __METHOD__ );
return self::$pageCount[$ns];
}
@@ -249,7 +251,6 @@ class SiteStats {
}
// Now check for underflow/overflow
foreach ( array(
- 'ss_total_views',
'ss_total_edits',
'ss_good_articles',
'ss_total_pages',
@@ -274,7 +275,7 @@ class SiteStatsInit {
// Various stats
private $mEdits = null, $mArticles = null, $mPages = null;
- private $mUsers = null, $mViews = null, $mFiles = null;
+ private $mUsers = null, $mFiles = null;
/**
* Constructor
@@ -349,15 +350,6 @@ class SiteStatsInit {
}
/**
- * Count views
- * @return int
- */
- public function views() {
- $this->mViews = $this->db->selectField( 'page', 'SUM(page_counter)', '', __METHOD__ );
- return $this->mViews;
- }
-
- /**
* Count total files
* @return int
*/
@@ -374,11 +366,10 @@ class SiteStatsInit {
* - Boolean: whether to use the master DB
* - DatabaseBase: database connection to use
* @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)
*/
public static function doAllAndCommit( $database, array $options = array() ) {
- $options += array( 'update' => false, 'views' => true, 'activeUsers' => false );
+ $options += array( 'update' => false, 'activeUsers' => false );
// Grab the object and count everything
$counter = new SiteStatsInit( $database );
@@ -389,11 +380,6 @@ class SiteStatsInit {
$counter->users();
$counter->files();
- // Only do views if we don't want to not count them
- if ( $options['views'] ) {
- $counter->views();
- }
-
$counter->refresh();
// Count active users if need be
@@ -403,8 +389,7 @@ class SiteStatsInit {
}
/**
- * Refresh site_stats. If you want ss_total_views to be updated, be sure to
- * call views() first.
+ * Refresh site_stats
*/
public function refresh() {
$values = array(
@@ -414,8 +399,6 @@ class SiteStatsInit {
'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()
);
$dbw = wfGetDB( DB_MASTER );
diff --git a/includes/StatCounter.php b/includes/StatCounter.php
deleted file mode 100644
index 5fc8f2f5..00000000
--- a/includes/StatCounter.php
+++ /dev/null
@@ -1,154 +0,0 @@
-<?php
-/**
- * @defgroup StatCounter StatCounter
- *
- * StatCounter is used to increment arbitrary keys for profiling reasons.
- * The key/values are persisted in several possible ways (see $wgStatsMethod).
- */
-
-/**
- * Aggregator for wfIncrStats() that batches updates per request.
- *
- * This 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 StatCounter
- * @author Aaron Schulz
- */
-
-/**
- * Aggregator for wfIncrStats() that batches updates per request.
- * This avoids spamming the collector many times for the same key.
- *
- * @ingroup StatCounter
- */
-class StatCounter {
- /** @var array */
- protected $deltas = array(); // (key => count)
-
- /** @var Config */
- protected $config;
-
- protected function __construct( Config $config ) {
- $this->config = $config;
- }
-
- /**
- * @return StatCounter
- */
- public static function singleton() {
- static $instance = null;
- if ( !$instance ) {
- $instance = new self(
- ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
- );
- }
- return $instance;
- }
-
- /**
- * Increment a key by delta $count
- *
- * @param string $key
- * @param int $count
- * @return void
- */
- public function incr( $key, $count = 1 ) {
- $this->deltas[$key] = isset( $this->deltas[$key] ) ? $this->deltas[$key] : 0;
- $this->deltas[$key] += $count;
- if ( PHP_SAPI === 'cli' ) {
- $this->flush();
- }
- }
-
- /**
- * Flush all pending deltas to persistent storage
- *
- * @return void
- */
- public function flush() {
- $statsMethod = $this->config->get( 'StatsMethod' );
- $deltas = array_filter( $this->deltas ); // remove 0 valued entries
- if ( $statsMethod === 'udp' ) {
- $this->sendDeltasUDP( $deltas );
- } elseif ( $statsMethod === 'cache' ) {
- $this->sendDeltasMemc( $deltas );
- } else {
- // disabled
- }
- $this->deltas = array();
- }
-
- /**
- * @param array $deltas
- * @return void
- */
- protected function sendDeltasUDP( array $deltas ) {
- $aggregateStatsID = $this->config->get( 'AggregateStatsID' );
- $id = strlen( $aggregateStatsID ) ? $aggregateStatsID : wfWikiID();
-
- $lines = array();
- foreach ( $deltas as $key => $count ) {
- $lines[] = sprintf( $this->config->get( 'StatsFormatString' ), $id, $count, $key );
- }
-
- if ( count( $lines ) ) {
- static $socket = null;
- if ( !$socket ) {
- $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- }
- $packet = '';
- $packets = array();
- foreach ( $lines as $line ) {
- if ( ( strlen( $packet ) + strlen( $line ) ) > 1450 ) {
- $packets[] = $packet;
- $packet = '';
- }
- $packet .= $line;
- }
- if ( $packet != '' ) {
- $packets[] = $packet;
- }
- foreach ( $packets as $packet ) {
- wfSuppressWarnings();
- socket_sendto(
- $socket,
- $packet,
- strlen( $packet ),
- 0,
- $this->config->get( 'UDPProfilerHost' ),
- $this->config->get( 'UDPProfilerPort' )
- );
- wfRestoreWarnings();
- }
- }
- }
-
- /**
- * @param array $deltas
- * @return void
- */
- protected function sendDeltasMemc( array $deltas ) {
- global $wgMemc;
-
- foreach ( $deltas as $key => $count ) {
- $ckey = wfMemcKey( 'stats', $key );
- if ( $wgMemc->incr( $ckey, $count ) === null ) {
- $wgMemc->add( $ckey, $count );
- }
- }
- }
-}
diff --git a/includes/Status.php b/includes/Status.php
index 1a72968b..cd10258d 100644
--- a/includes/Status.php
+++ b/includes/Status.php
@@ -38,41 +38,58 @@
* so that a lack of error-handling will be explicit.
*/
class Status {
- /** @var bool */
- public $ok = true;
+ /** @var StatusValue */
+ protected $sv;
/** @var mixed */
public $value;
-
- /** Counters for batch operations */
- /** @var int */
+ /** @var array Map of (key => bool) to indicate success of each part of batch operations */
+ public $success = array();
+ /** @var int Counter for batch operations */
public $successCount = 0;
-
- /** @var int */
+ /** @var int Counter for batch operations */
public $failCount = 0;
- /** Array to indicate which items of the batch operations were successful */
- /** @var array */
- public $success = array();
-
- /** @var array */
- public $errors = array();
-
/** @var callable */
public $cleanCallback = false;
/**
+ * @param StatusValue $sv [optional]
+ */
+ public function __construct( StatusValue $sv = null ) {
+ $this->sv = ( $sv === null ) ? new StatusValue() : $sv;
+ // B/C field aliases
+ $this->value =& $this->sv->value;
+ $this->successCount =& $this->sv->successCount;
+ $this->failCount =& $this->sv->failCount;
+ $this->success =& $this->sv->success;
+ }
+
+ /**
+ * Succinct helper method to wrap a StatusValue
+ *
+ * This is is useful when formatting StatusValue objects:
+ * <code>
+ * $this->getOutput()->addHtml( Status::wrap( $sv )->getHTML() );
+ * </code>
+ *
+ * @param StatusValue|Status $sv
+ * @return Status
+ */
+ public static function wrap( $sv ) {
+ return $sv instanceof Status ? $sv : new self( $sv );
+ }
+
+ /**
* Factory function for fatal errors
*
* @param string|Message $message Message name or object
* @return Status
*/
- static function newFatal( $message /*, parameters...*/ ) {
- $params = func_get_args();
- $result = new self;
- call_user_func_array( array( &$result, 'error' ), $params );
- $result->ok = false;
- return $result;
+ public static function newFatal( $message /*, parameters...*/ ) {
+ return new self( call_user_func_array(
+ array( 'StatusValue', 'newFatal' ), func_get_args()
+ ) );
}
/**
@@ -81,10 +98,11 @@ class Status {
* @param mixed $value
* @return Status
*/
- static function newGood( $value = null ) {
- $result = new self;
- $result->value = $value;
- return $result;
+ public static function newGood( $value = null ) {
+ $sv = new StatusValue();
+ $sv->value = $value;
+
+ return new self( $sv );
}
/**
@@ -94,8 +112,7 @@ class Status {
* @param mixed $value
*/
public function setResult( $ok, $value = null ) {
- $this->ok = $ok;
- $this->value = $value;
+ $this->sv->setResult( $ok, $value );
}
/**
@@ -105,7 +122,7 @@ class Status {
* @return bool
*/
public function isGood() {
- return $this->ok && !$this->errors;
+ return $this->sv->isGood();
}
/**
@@ -114,7 +131,7 @@ class Status {
* @return bool
*/
public function isOK() {
- return $this->ok;
+ return $this->sv->isOK();
}
/**
@@ -123,11 +140,7 @@ class Status {
* @param string|Message $message Message name or object
*/
public function warning( $message /*, parameters... */ ) {
- $params = array_slice( func_get_args(), 1 );
- $this->errors[] = array(
- 'type' => 'warning',
- 'message' => $message,
- 'params' => $params );
+ call_user_func_array( array( $this->sv, 'warning' ), func_get_args() );
}
/**
@@ -137,11 +150,7 @@ class Status {
* @param string|Message $message Message name or object
*/
public function error( $message /*, parameters... */ ) {
- $params = array_slice( func_get_args(), 1 );
- $this->errors[] = array(
- 'type' => 'error',
- 'message' => $message,
- 'params' => $params );
+ call_user_func_array( array( $this->sv, 'error' ), func_get_args() );
}
/**
@@ -151,26 +160,14 @@ class Status {
* @param string|Message $message Message name or object
*/
public function fatal( $message /*, parameters... */ ) {
- $params = array_slice( func_get_args(), 1 );
- $this->errors[] = array(
- 'type' => 'error',
- 'message' => $message,
- 'params' => $params );
- $this->ok = false;
- }
-
- /**
- * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
- */
- public function __wakeup() {
- $this->cleanCallback = false;
+ call_user_func_array( array( $this->sv, 'fatal' ), func_get_args() );
}
/**
* @param array $params
* @return array
*/
- protected function cleanParams( $params ) {
+ protected function cleanParams( array $params ) {
if ( !$this->cleanCallback ) {
return $params;
}
@@ -184,30 +181,32 @@ class Status {
/**
* Get the error list as a wikitext formatted list
*
- * @param string $shortContext A short enclosing context message name, to
+ * @param string|bool $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|bool $longContext A long enclosing context message name, for a list
* @return string
*/
public function getWikiText( $shortContext = false, $longContext = false ) {
- if ( count( $this->errors ) == 0 ) {
- if ( $this->ok ) {
- $this->fatal( 'internalerror_info',
+ $rawErrors = $this->sv->getErrors();
+ if ( count( $rawErrors ) == 0 ) {
+ if ( $this->sv->isOK() ) {
+ $this->sv->fatal( 'internalerror_info',
__METHOD__ . " called for a good result, this is incorrect\n" );
} else {
- $this->fatal( 'internalerror_info',
+ $this->sv->fatal( 'internalerror_info',
__METHOD__ . ": Invalid result object: no error text but not OK\n" );
}
+ $rawErrors = $this->sv->getErrors(); // just added a fatal
}
- if ( count( $this->errors ) == 1 ) {
- $s = $this->getErrorMessage( $this->errors[0] )->plain();
+ if ( count( $rawErrors ) == 1 ) {
+ $s = $this->getErrorMessage( $rawErrors[0] )->plain();
if ( $shortContext ) {
$s = wfMessage( $shortContext, $s )->plain();
} elseif ( $longContext ) {
$s = wfMessage( $longContext, "* $s\n" )->plain();
}
} else {
- $errors = $this->getErrorMessageArray( $this->errors );
+ $errors = $this->getErrorMessageArray( $rawErrors );
foreach ( $errors as &$error ) {
$error = $error->plain();
}
@@ -232,17 +231,19 @@ class Status {
* @return Message
*/
public function getMessage( $shortContext = false, $longContext = false ) {
- if ( count( $this->errors ) == 0 ) {
- if ( $this->ok ) {
- $this->fatal( 'internalerror_info',
+ $rawErrors = $this->sv->getErrors();
+ if ( count( $rawErrors ) == 0 ) {
+ if ( $this->sv->isOK() ) {
+ $this->sv->fatal( 'internalerror_info',
__METHOD__ . " called for a good result, this is incorrect\n" );
} else {
- $this->fatal( 'internalerror_info',
+ $this->sv->fatal( 'internalerror_info',
__METHOD__ . ": Invalid result object: no error text but not OK\n" );
}
+ $rawErrors = $this->sv->getErrors(); // just added a fatal
}
- if ( count( $this->errors ) == 1 ) {
- $s = $this->getErrorMessage( $this->errors[0] );
+ if ( count( $rawErrors ) == 1 ) {
+ $s = $this->getErrorMessage( $rawErrors[0] );
if ( $shortContext ) {
$s = wfMessage( $shortContext, $s );
} elseif ( $longContext ) {
@@ -251,7 +252,7 @@ class Status {
$s = wfMessage( $longContext, $wrapper );
}
} else {
- $msgs = $this->getErrorMessageArray( $this->errors );
+ $msgs = $this->getErrorMessageArray( $rawErrors );
$msgCount = count( $msgs );
if ( $shortContext ) {
@@ -330,13 +331,7 @@ class Status {
* @param bool $overwriteValue Whether to override the "value" member
*/
public function merge( $other, $overwriteValue = false ) {
- $this->errors = array_merge( $this->errors, $other->errors );
- $this->ok = $this->ok && $other->ok;
- if ( $overwriteValue ) {
- $this->value = $other->value;
- }
- $this->successCount += $other->successCount;
- $this->failCount += $other->failCount;
+ $this->sv->merge( $other->sv, $overwriteValue );
}
/**
@@ -344,9 +339,10 @@ 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.
+ * @deprecated 1.25
*/
public function getErrorsArray() {
- return $this->getStatusArray( "error" );
+ return $this->getStatusArray( 'error' );
}
/**
@@ -354,21 +350,26 @@ 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.
+ * @deprecated 1.25
*/
public function getWarningsArray() {
- return $this->getStatusArray( "warning" );
+ return $this->getStatusArray( 'warning' );
}
/**
- * Returns a list of status messages of the given type
+ * Returns a list of status messages of the given type (or all if false)
+ *
+ * @note: this handles RawMessage poorly
+ *
* @param string $type
* @return array
*/
- protected function getStatusArray( $type ) {
+ protected function getStatusArray( $type = false ) {
$result = array();
- foreach ( $this->errors as $error ) {
- if ( $error['type'] === $type ) {
- if ( $error['message'] instanceof Message ) {
+
+ foreach ( $this->sv->getErrors() as $error ) {
+ if ( $type === false || $error['type'] === $type ) {
+ if ( $error['message'] instanceof MessageSpecifier ) {
$result[] = array_merge(
array( $error['message']->getKey() ),
$error['message']->getParams()
@@ -393,13 +394,7 @@ class Status {
* @return array
*/
public function getErrorsByType( $type ) {
- $result = array();
- foreach ( $this->errors as $error ) {
- if ( $error['type'] === $type ) {
- $result[] = $error;
- }
- }
- return $result;
+ return $this->sv->getErrorsByType( $type );
}
/**
@@ -410,19 +405,7 @@ class Status {
* @return bool
*/
public function hasMessage( $message ) {
- if ( $message instanceof Message ) {
- $message = $message->getKey();
- }
- foreach ( $this->errors as $error ) {
- if ( $error['message'] instanceof Message
- && $error['message']->getKey() === $message
- ) {
- return true;
- } elseif ( $error['message'] === $message ) {
- return true;
- }
- }
- return false;
+ return $this->sv->hasMessage( $message );
}
/**
@@ -437,20 +420,67 @@ class Status {
* @return bool Return true if the replacement was done, false otherwise.
*/
public function replaceMessage( $source, $dest ) {
- $replaced = false;
- foreach ( $this->errors as $index => $error ) {
- if ( $error['message'] === $source ) {
- $this->errors[$index]['message'] = $dest;
- $replaced = true;
- }
- }
- return $replaced;
+ return $this->sv->replaceMessage( $source, $dest );
}
/**
* @return mixed
*/
public function getValue() {
- return $this->value;
+ return $this->sv->getValue();
+ }
+
+ /**
+ * Backwards compatibility logic
+ *
+ * @param string $name
+ */
+ function __get( $name ) {
+ if ( $name === 'ok' ) {
+ return $this->sv->isOK();
+ } elseif ( $name === 'errors' ) {
+ return $this->sv->getErrors();
+ }
+ throw new Exception( "Cannot get '$name' property." );
+ }
+
+ /**
+ * Backwards compatibility logic
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ function __set( $name, $value ) {
+ if ( $name === 'ok' ) {
+ $this->sv->setOK( $value );
+ } elseif ( !property_exists( $this, $name ) ) {
+ // Caller is using undeclared ad-hoc properties
+ $this->$name = $value;
+ } else {
+ throw new Exception( "Cannot set '$name' property." );
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString() {
+ return $this->sv->__toString();
+ }
+
+ /**
+ * Don't save the callback when serializing, because Closures can't be
+ * serialized and we're going to clear it in __wakeup anyway.
+ */
+ function __sleep() {
+ $keys = array_keys( get_object_vars( $this ) );
+ return array_diff( $keys, array( 'cleanCallback' ) );
+ }
+
+ /**
+ * Sanitize the callback parameter on wakeup, to avoid arbitrary execution.
+ */
+ function __wakeup() {
+ $this->cleanCallback = false;
}
}
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 25031501..a52b25b0 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -39,10 +39,8 @@ class StreamFile {
* @return bool Success
*/
public static function stream( $fname, $headers = array(), $sendErrors = true ) {
- wfProfileIn( __METHOD__ );
if ( FileBackend::isStoragePath( $fname ) ) { // sanity
- wfProfileOut( __METHOD__ );
throw new MWException( __FUNCTION__ . " given storage path '$fname'." );
}
@@ -54,14 +52,11 @@ class StreamFile {
if ( $res == self::NOT_MODIFIED ) {
$ok = true; // use client cache
} elseif ( $res == self::READY_STREAM ) {
- wfProfileIn( __METHOD__ . '-send' );
$ok = readfile( $fname );
- wfProfileOut( __METHOD__ . '-send' );
} else {
$ok = false; // failed
}
- wfProfileOut( __METHOD__ );
return $ok;
}
diff --git a/includes/StubObject.php b/includes/StubObject.php
index 8878660b..2dfcdc2f 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -110,7 +110,11 @@ class StubObject {
* @return object
*/
public function _newObject() {
- return MWFunction::newObj( $this->class, $this->params );
+ return ObjectFactory::getObjectFromSpec( array(
+ 'class' => $this->class,
+ 'args' => $this->params,
+ 'closure_expansion' => false,
+ ) );
}
/**
@@ -146,10 +150,8 @@ class StubObject {
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->global}->$name from $caller\n" );
}
@@ -157,7 +159,6 @@ class StubObject {
. "\${$this->global}::$name from $caller\n" );
$GLOBALS[$this->global] = $this->_newObject();
--$recursionLevel;
- wfProfileOut( $fname );
return $GLOBALS[$this->global];
}
}
@@ -179,6 +180,24 @@ class StubUserLang extends StubObject {
}
/**
+ * Call Language::findVariantLink after unstubbing $wgLang.
+ *
+ * This method is implemented with a full signature rather than relying on
+ * __call so that the pass-by-reference signature of the proxied method is
+ * honored.
+ *
+ * @param string &$link The name of the link
+ * @param Title &$nt The title object of the link
+ * @param bool $ignoreOtherCond To disable other conditions when
+ * we need to transclude a template or update a category's link
+ */
+ public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
+ global $wgLang;
+ $this->_unstub( 'findVariantLink', 3 );
+ return $wgLang->findVariantLink( $link, $nt, $ignoreOtherCond );
+ }
+
+ /**
* @return Language
*/
public function _newObject() {
diff --git a/includes/TemplateParser.php b/includes/TemplateParser.php
new file mode 100644
index 00000000..3de70fa2
--- /dev/null
+++ b/includes/TemplateParser.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * Handles compiling Mustache templates into PHP rendering functions
+ *
+ * This 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.25
+ */
+class TemplateParser {
+ /**
+ * @var string The path to the Mustache templates
+ */
+ protected $templateDir;
+
+ /**
+ * @var callable[] Array of cached rendering functions
+ */
+ protected $renderers;
+
+ /**
+ * @var bool Always compile template files
+ */
+ protected $forceRecompile = false;
+
+ /**
+ * @param string $templateDir
+ * @param boolean $forceRecompile
+ */
+ public function __construct( $templateDir = null, $forceRecompile = false ) {
+ $this->templateDir = $templateDir ? $templateDir : __DIR__ . '/templates';
+ $this->forceRecompile = $forceRecompile;
+ }
+
+ /**
+ * Constructs the location of the the source Mustache template
+ * @param string $templateName The name of the template
+ * @return string
+ * @throws UnexpectedValueException Disallows upwards directory traversal via $templateName
+ */
+ protected function getTemplateFilename( $templateName ) {
+ // Prevent upwards directory traversal using same methods as Title::secureAndSplit
+ if (
+ strpos( $templateName, '.' ) !== false &&
+ (
+ $templateName === '.' || $templateName === '..' ||
+ strpos( $templateName, './' ) === 0 ||
+ strpos( $templateName, '../' ) === 0 ||
+ strpos( $templateName, '/./' ) !== false ||
+ strpos( $templateName, '/../' ) !== false ||
+ substr( $templateName, -2 ) === '/.' ||
+ substr( $templateName, -3 ) === '/..'
+ )
+ ) {
+ throw new UnexpectedValueException( "Malformed \$templateName: $templateName" );
+ }
+
+ return "{$this->templateDir}/{$templateName}.mustache";
+ }
+
+ /**
+ * Returns a given template function if found, otherwise throws an exception.
+ * @param string $templateName The name of the template (without file suffix)
+ * @return callable
+ * @throws RuntimeException
+ */
+ protected function getTemplate( $templateName ) {
+ // If a renderer has already been defined for this template, reuse it
+ if ( isset( $this->renderers[$templateName] ) && is_callable( $this->renderers[$templateName] ) ) {
+ return $this->renderers[$templateName];
+ }
+
+ $filename = $this->getTemplateFilename( $templateName );
+
+ if ( !file_exists( $filename ) ) {
+ throw new RuntimeException( "Could not locate template: {$filename}" );
+ }
+
+ // Read the template file
+ $fileContents = file_get_contents( $filename );
+
+ // Generate a quick hash for cache invalidation
+ $fastHash = md5( $fileContents );
+
+ // Fetch a secret key for building a keyed hash of the PHP code
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ $secretKey = $config->get( 'SecretKey' );
+
+ if ( $secretKey ) {
+ // See if the compiled PHP code is stored in cache.
+ // CACHE_ACCEL throws an exception if no suitable object cache is present, so fall
+ // back to CACHE_ANYTHING.
+ $cache = ObjectCache::newAccelerator( array(), CACHE_ANYTHING );
+ $key = wfMemcKey( 'template', $templateName, $fastHash );
+ $code = $this->forceRecompile ? null : $cache->get( $key );
+
+ if ( !$code ) {
+ $code = $this->compileForEval( $fileContents, $filename );
+
+ // Prefix the cached code with a keyed hash (64 hex chars) as an integrity check
+ $cache->set( $key, hash_hmac( 'sha256', $code, $secretKey ) . $code );
+ } else {
+ // Verify the integrity of the cached PHP code
+ $keyedHash = substr( $code, 0, 64 );
+ $code = substr( $code, 64 );
+ if ( $keyedHash !== hash_hmac( 'sha256', $code, $secretKey ) ) {
+ // Generate a notice if integrity check fails
+ trigger_error( "Template failed integrity check: {$filename}" );
+ }
+ }
+ // If there is no secret key available, don't use cache
+ } else {
+ $code = $this->compileForEval( $fileContents, $filename );
+ }
+
+ $renderer = eval( $code );
+ if ( !is_callable( $renderer ) ) {
+ throw new RuntimeException( "Requested template, {$templateName}, is not callable" );
+ }
+ return $this->renderers[$templateName] = $renderer;
+ }
+
+ /**
+ * Wrapper for compile() function that verifies successful compilation and strips
+ * out the '<?php' part so that the code is ready for eval()
+ * @param string $fileContents Mustache code
+ * @param string $filename Name of the template
+ * @return string PHP code (without '<?php')
+ * @throws RuntimeException
+ */
+ protected function compileForEval( $fileContents, $filename ) {
+ // Compile the template into PHP code
+ $code = $this->compile( $fileContents );
+
+ if ( !$code ) {
+ throw new RuntimeException( "Could not compile template: {$filename}" );
+ }
+
+ // Strip the "<?php" added by lightncandy so that it can be eval()ed
+ if ( substr( $code, 0, 5 ) === '<?php' ) {
+ $code = substr( $code, 5 );
+ }
+
+ return $code;
+ }
+
+ /**
+ * Compile the Mustache code into PHP code using LightnCandy
+ * @param string $code Mustache code
+ * @return string PHP code (with '<?php')
+ * @throws RuntimeException
+ */
+ protected function compile( $code ) {
+ if ( !class_exists( 'LightnCandy' ) ) {
+ throw new RuntimeException( 'LightnCandy class not defined' );
+ }
+ return LightnCandy::compile(
+ $code,
+ array(
+ // Do not add more flags here without discussion.
+ // If you do add more flags, be sure to update unit tests as well.
+ 'flags' => LightnCandy::FLAG_ERROR_EXCEPTION
+ )
+ );
+ }
+
+ /**
+ * Returns HTML for a given template by calling the template function with the given args
+ *
+ * @code
+ * echo $templateParser->processTemplate(
+ * 'ExampleTemplate',
+ * array(
+ * 'username' => $user->getName(),
+ * 'message' => 'Hello!'
+ * )
+ * );
+ * @endcode
+ * @param string $templateName The name of the template
+ * @param mixed $args
+ * @param array $scopes
+ * @return string
+ */
+ public function processTemplate( $templateName, $args, array $scopes = array() ) {
+ $template = $this->getTemplate( $templateName );
+ return call_user_func( $template, $args, $scopes );
+ }
+}
diff --git a/includes/Title.php b/includes/Title.php
index 74d78ba5..d8976635 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -29,8 +29,6 @@
* 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 {
/** @var MapCacheLRU */
@@ -258,12 +256,14 @@ class Title {
* 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
+ * @throws InvalidArgumentException
* @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' );
+ throw new InvalidArgumentException( '$text must be a string.' );
+ } elseif ( !is_string( $text ) ) {
+ wfWarn( __METHOD__ . ': $text must be a string. This will throw an InvalidArgumentException in future.' );
}
$cache = self::getTitleCache();
@@ -503,7 +503,7 @@ class Title {
}
$t = new Title();
- $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
+ $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
if ( $t->secureAndSplit() ) {
return $t;
} else {
@@ -614,28 +614,13 @@ 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.
*
- * @todo move this into MediaWikiTitleCodec
+ * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead
*
* @return string Regex string
*/
static function getTitleInvalidRegex() {
- static $rxTc = false;
- if ( !$rxTc ) {
- # Matching titles will be held as illegal.
- $rxTc = '/' .
- # Any character not allowed is forbidden...
- '[^' . self::legalChars() . ']' .
- # URL percent encoding sequences interfere with the ability
- # to round-trip titles -- you can't link to them consistently.
- '|%[0-9A-Fa-f]{2}' .
- # XML/HTML character references produce similar issues.
- '|&[A-Za-z0-9\x80-\xff]+;' .
- '|&#[0-9]+;' .
- '|&#x[0-9A-Fa-f]+;' .
- '/S';
- }
-
- return $rxTc;
+ wfDeprecated( __METHOD__, '1.25' );
+ return MediaWikiTitleCodec::getTitleInvalidRegex();
}
/**
@@ -747,12 +732,20 @@ class Title {
* @param string $title The DB key form the title
* @param string $fragment The link fragment (after the "#")
* @param string $interwiki The interwiki prefix
+ * @param bool $canoncialNamespace If true, use the canonical name for
+ * $ns instead of the localized version.
* @return string The prefixed form of the title
*/
- public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
+ public static function makeName( $ns, $title, $fragment = '', $interwiki = '',
+ $canoncialNamespace = false
+ ) {
global $wgContLang;
- $namespace = $wgContLang->getNsText( $ns );
+ if ( $canoncialNamespace ) {
+ $namespace = MWNamespace::getCanonicalName( $ns );
+ } else {
+ $namespace = $wgContLang->getNsText( $ns );
+ }
$name = $namespace == '' ? $title : "$namespace:$title";
if ( strval( $interwiki ) != '' ) {
$name = "$interwiki:$name";
@@ -795,7 +788,8 @@ class Title {
/**
* Determine whether the object refers to a page within
- * this project.
+ * this project (either this wiki or a wiki with a local
+ * interwiki, see https://www.mediawiki.org/wiki/Manual:Interwiki_table#iw_local )
*
* @return bool True if this is an in-project interwiki link or a wikilink, false otherwise
*/
@@ -948,9 +942,9 @@ class Title {
* @return string Content model id
*/
public function getContentModel( $flags = 0 ) {
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
}
@@ -1035,7 +1029,6 @@ class Title {
* Is this in a namespace that allows actual pages?
*
* @return bool
- * @internal note -- uses hardcoded namespace index instead of constants
*/
public function canExist() {
return $this->mNamespace >= NS_MAIN;
@@ -1171,7 +1164,7 @@ class Title {
}
$result = true;
- wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
+ Hooks::run( 'TitleIsMovable', array( $this, &$result ) );
return $result;
}
@@ -1241,9 +1234,9 @@ class Title {
# @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.
+ # method to return true even outside the MediaWiki namespace.
- wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
+ Hooks::run( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ), '1.25' );
return $isCssOrJsPage;
}
@@ -1327,6 +1320,25 @@ class Title {
}
/**
+ * Get the other title for this page, if this is a subject page
+ * get the talk page, if it is a subject page get the talk page
+ *
+ * @since 1.25
+ * @throws MWException
+ * @return Title
+ */
+ public function getOtherPage() {
+ if ( $this->isSpecialPage() ) {
+ throw new MWException( 'Special pages cannot have other pages' );
+ }
+ if ( $this->isTalkPage() ) {
+ return $this->getSubjectPage();
+ } else {
+ return $this->getTalkPage();
+ }
+ }
+
+ /**
* Get the default namespace index, for when there is no namespace
*
* @return int Default namespace index
@@ -1650,7 +1662,7 @@ class Title {
# Finally, add the fragment.
$url .= $this->getFragmentForURL();
- wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetFullURL', array( &$this, &$url, $query ) );
return $url;
}
@@ -1696,7 +1708,7 @@ class Title {
$dbkey = wfUrlencode( $this->getPrefixedDBkey() );
if ( $query == '' ) {
$url = str_replace( '$1', $dbkey, $wgArticlePath );
- wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
+ Hooks::run( 'GetLocalURL::Article', array( &$this, &$url ) );
} else {
global $wgVariantArticlePath, $wgActionPaths, $wgContLang;
$url = false;
@@ -1741,7 +1753,7 @@ class Title {
}
}
- wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
// @todo FIXME: This causes breakage in various places when we
// actually expected a local URL and end up with dupe prefixes.
@@ -1749,7 +1761,7 @@ class Title {
$url = $wgServer . $url;
}
}
- wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetLocalURL', array( &$this, &$url, $query ) );
return $url;
}
@@ -1770,7 +1782,6 @@ class Title {
* @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->hasFragment() ) {
@@ -1778,7 +1789,6 @@ class Title {
} else {
$ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -1799,7 +1809,7 @@ class Title {
$query = self::fixUrlQueryArgs( $query, $query2 );
$server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
$url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
- wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetInternalURL', array( &$this, &$url, $query ) );
return $url;
}
@@ -1817,7 +1827,7 @@ class Title {
public function getCanonicalURL( $query = '', $query2 = false ) {
$query = self::fixUrlQueryArgs( $query, $query2 );
$url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
- wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
+ Hooks::run( 'GetCanonicalURL', array( &$this, &$url, $query ) );
return $url;
}
@@ -1878,18 +1888,16 @@ class Title {
* @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.
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @return bool
*/
- public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
+ public function userCan( $action, $user = null, $rigor = 'secure' ) {
if ( !$user instanceof User ) {
global $wgUser;
$user = $wgUser;
}
- return !count( $this->getUserPermissionsErrorsInternal(
- $action, $user, $doExpensiveQueries, true ) );
+ return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
}
/**
@@ -1899,16 +1907,19 @@ class Title {
*
* @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 string $rigor One of (quick,full,secure)
+ * - quick : does cheap permission checks from slaves (usable for GUI creation)
+ * - full : does cheap and expensive checks possibly from a slave
+ * - secure : does cheap and expensive checks, using the master as needed
+ * @param bool $short Set this to true to stop after the first permission error.
* @param array $ignoreErrors Array of Strings Set this to a list of message keys
* whose corresponding errors may be ignored.
* @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, $rigor = 'secure', $ignoreErrors = array()
) {
- $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
+ $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
// Remove the errors being ignored.
foreach ( $errors as $index => $error ) {
@@ -1928,16 +1939,14 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @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, $rigor, $short ) {
+ if ( !Hooks::run( 'TitleQuickPermissions',
+ array( $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ) )
) {
return $errors;
}
@@ -2028,26 +2037,26 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
// Use getUserPermissionsErrors instead
$result = '';
- if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
+ if ( !Hooks::run( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
return $result ? array() : array( array( 'badaccess-group0' ) );
}
// Check getUserPermissionsErrors hook
- if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
+ if ( !Hooks::run( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
$errors = $this->resultToError( $errors, $result );
}
// Check getUserPermissionsErrorsExpensive hook
if (
- $doExpensiveQueries
+ $rigor !== 'quick'
&& !( $short && count( $errors ) > 0 )
- && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
+ && !Hooks::run( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
) {
$errors = $this->resultToError( $errors, $result );
}
@@ -2061,14 +2070,12 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkSpecialsAndNSPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
+ private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
# Only 'createaccount' can be performed on special pages,
# which don't actually exist in the DB.
if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
@@ -2092,12 +2099,12 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
# Protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: right 'editusercssjs' is deprecated, for backward compatibility only
@@ -2128,12 +2135,12 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
foreach ( $this->getRestrictions( $action ) as $right ) {
// Backwards compatibility, rewrite sysop -> editprotected
if ( $right == 'sysop' ) {
@@ -2162,15 +2169,13 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkCascadingSourcesRestrictions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
- if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
+ private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
+ if ( $rigor !== 'quick' && !$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
# included by multiple cascade-protected pages. So just restrict
@@ -2211,39 +2216,29 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkActionPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
+ private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
global $wgDeleteRevisionsLimit, $wgLang;
if ( $action == 'protect' ) {
- if ( count( $this->getUserPermissionsErrorsInternal( 'edit',
- $user, $doExpensiveQueries, true ) )
- ) {
+ if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
// If they can't edit, they shouldn't protect.
$errors[] = array( 'protect-cantedit' );
}
} elseif ( $action == 'create' ) {
$title_protection = $this->getTitleProtection();
if ( $title_protection ) {
- if ( $title_protection['pt_create_perm'] == 'sysop' ) {
- $title_protection['pt_create_perm'] = 'editprotected'; // B/C
- }
- 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'] )
+ if ( $title_protection['permission'] == ''
+ || !$user->isAllowed( $title_protection['permission'] )
) {
$errors[] = array(
'titleprotected',
- User::whoIs( $title_protection['pt_user'] ),
- $title_protection['pt_reason']
+ User::whoIs( $title_protection['user'] ),
+ $title_protection['reason']
);
}
}
@@ -2263,17 +2258,16 @@ class Title {
$errors[] = array( 'immobile-target-page' );
}
} elseif ( $action == 'delete' ) {
- $tempErrors = $this->checkPageRestrictions( 'edit',
- $user, array(), $doExpensiveQueries, true );
+ $tempErrors = $this->checkPageRestrictions( 'edit', $user, array(), $rigor, true );
if ( !$tempErrors ) {
$tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
- $user, $tempErrors, $doExpensiveQueries, true );
+ $user, $tempErrors, $rigor, true );
}
if ( $tempErrors ) {
// If protection keeps them from editing, they shouldn't be able to delete.
$errors[] = array( 'deleteprotected' );
}
- if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
+ if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
&& !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
) {
$errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
@@ -2288,15 +2282,15 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
// Account creation blocks handled at userlogin.
// Unblocking handled in SpecialUnblock
- if ( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
+ if ( $rigor === 'quick' || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
return $errors;
}
@@ -2306,10 +2300,13 @@ class Title {
$errors[] = array( 'confirmedittext' );
}
- if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
+ $useSlave = ( $rigor !== 'secure' );
+ if ( ( $action == 'edit' || $action == 'create' )
+ && !$user->isBlockedFrom( $this, $useSlave )
+ ) {
// Don't block the user from editing their own talk page unless they've been
// explicitly blocked from that too.
- } elseif ( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
+ } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
// @todo FIXME: Pass the relevant context into this function.
$errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
}
@@ -2323,12 +2320,12 @@ class Title {
* @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 string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
global $wgWhitelistRead, $wgWhitelistReadRegexp;
$whitelisted = false;
@@ -2386,7 +2383,7 @@ class Title {
if ( !$whitelisted ) {
# If the title is not whitelisted, give extensions a chance to do so...
- wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
+ Hooks::run( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
if ( !$whitelisted ) {
$errors[] = $this->missingPermissionError( $action, $short );
}
@@ -2431,14 +2428,23 @@ class Title {
*
* @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 string $rigor One of (quick,full,secure)
+ * - quick : does cheap permission checks from slaves (usable for GUI creation)
+ * - full : does cheap and expensive checks possibly from a slave
+ * - secure : does cheap and expensive checks, using the master as needed
* @param bool $short Set this to true to stop after the first permission error.
* @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, $rigor = 'secure', $short = false
) {
- wfProfileIn( __METHOD__ );
+ if ( $rigor === true ) {
+ $rigor = 'secure'; // b/c
+ } elseif ( $rigor === false ) {
+ $rigor = 'quick'; // b/c
+ } elseif ( !in_array( $rigor, array( 'quick', 'full', 'secure' ) ) ) {
+ throw new Exception( "Invalid rigor parameter '$rigor'." );
+ }
# Read has special handling
if ( $action == 'read' ) {
@@ -2476,10 +2482,9 @@ class Title {
while ( count( $checks ) > 0 &&
!( $short && count( $errors ) > 0 ) ) {
$method = array_shift( $checks );
- $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
+ $errors = $this->$method( $action, $user, $errors, $rigor, $short );
}
- wfProfileOut( __METHOD__ );
return $errors;
}
@@ -2520,7 +2525,7 @@ class Title {
$types = array_diff( $types, array( 'upload' ) );
}
- wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
+ Hooks::run( 'TitleGetRestrictionTypes', array( $this, &$types ) );
wfDebug( __METHOD__ . ': applicable restrictions to [[' .
$this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
@@ -2535,7 +2540,7 @@ class Title {
* @return array|bool An associative array representing any existent title
* protection, or false if there's none.
*/
- private function getTitleProtection() {
+ public function getTitleProtection() {
// Can't protect pages in special namespaces
if ( $this->getNamespace() < 0 ) {
return false;
@@ -2550,13 +2555,27 @@ class Title {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'protected_titles',
- array( 'pt_user', 'pt_reason', 'pt_expiry', 'pt_create_perm' ),
+ array(
+ 'user' => 'pt_user',
+ 'reason' => 'pt_reason',
+ 'expiry' => 'pt_expiry',
+ 'permission' => 'pt_create_perm'
+ ),
array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
__METHOD__
);
// fetchRow returns false if there are no rows.
- $this->mTitleProtection = $dbr->fetchRow( $res );
+ $row = $dbr->fetchRow( $res );
+ if ( $row ) {
+ if ( $row['permission'] == 'sysop' ) {
+ $row['permission'] = 'editprotected'; // B/C
+ }
+ if ( $row['permission'] == 'autoconfirmed' ) {
+ $row['permission'] = 'editsemiprotected'; // B/C
+ }
+ }
+ $this->mTitleProtection = $row;
}
return $this->mTitleProtection;
}
@@ -2701,8 +2720,6 @@ class Title {
return array( $this->mHasCascadingRestrictions, $pagerestrictions );
}
- wfProfileIn( __METHOD__ );
-
$dbr = wfGetDB( DB_SLAVE );
if ( $this->getNamespace() == NS_FILE ) {
@@ -2735,7 +2752,6 @@ class Title {
$sources = $getPages ? array() : false;
$now = wfTimestampNow();
- $purgeExpired = false;
foreach ( $res as $row ) {
$expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
@@ -2761,14 +2777,8 @@ class Title {
} else {
$sources = true;
}
- } else {
- // Trigger lazy purge of expired restrictions from the db
- $purgeExpired = true;
}
}
- if ( $purgeExpired ) {
- Title::purgeExpiredRestrictions();
- }
if ( $getPages ) {
$this->mCascadeSources = $sources;
@@ -2777,7 +2787,6 @@ class Title {
$this->mHasCascadingRestrictions = $sources;
}
- wfProfileOut( __METHOD__ );
return array( $sources, $pagerestrictions );
}
@@ -2796,8 +2805,10 @@ class Title {
* Accessor/initialisation for mRestrictions
*
* @param string $action Action that permission needs to be checked for
- * @return array Restriction levels needed to take the action. All levels
- * are required.
+ * @return array Restriction levels needed to take the action. All levels are
+ * required. Note that restriction levels are normally user rights, but 'sysop'
+ * and 'autoconfirmed' are also allowed for backwards compatibility. These should
+ * be mapped to 'editprotected' and 'editsemiprotected' respectively.
*/
public function getRestrictions( $action ) {
if ( !$this->mRestrictionsLoaded ) {
@@ -2918,7 +2929,6 @@ class Title {
if ( count( $rows ) ) {
# Current system - load second to make them override.
$now = wfTimestampNow();
- $purgeExpired = false;
# Cycle through all the restrictions.
foreach ( $rows as $row ) {
@@ -2938,15 +2948,8 @@ class Title {
$this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
$this->mCascadeRestriction |= $row->pr_cascade;
- } else {
- // Trigger a lazy purge of expired restrictions
- $purgeExpired = true;
}
}
-
- if ( $purgeExpired ) {
- Title::purgeExpiredRestrictions();
- }
}
$this->mRestrictionsLoaded = true;
@@ -2977,14 +2980,13 @@ class Title {
if ( $title_protection ) {
$now = wfTimestampNow();
- $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
+ $expiry = $wgContLang->formatExpiry( $title_protection['expiry'], TS_MW );
if ( !$expiry || $expiry > $now ) {
// Apply the restrictions
$this->mRestrictionsExpiry['create'] = $expiry;
- $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
+ $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
} else { // Get rid of the old restrictions
- Title::purgeExpiredRestrictions();
$this->mTitleProtection = false;
}
} else {
@@ -3170,13 +3172,13 @@ class Title {
if ( !is_null( $this->mRedirect ) ) {
return $this->mRedirect;
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mRedirect = false;
return $this->mRedirect;
}
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
if ( $cached === null ) {
# Trust LinkCache's state over our own
@@ -3205,12 +3207,12 @@ class Title {
if ( $this->mLength != -1 ) {
return $this->mLength;
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mLength = 0;
return $this->mLength;
}
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
@@ -3233,13 +3235,12 @@ class Title {
if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
return intval( $this->mLatestID );
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mLatestID = 0;
return $this->mLatestID;
}
$linkCache = LinkCache::singleton();
- $linkCache->addLinkObj( $this );
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
@@ -3283,6 +3284,14 @@ class Title {
$this->mIsBigDeletion = null;
}
+ public static function clearCaches() {
+ $linkCache = LinkCache::singleton();
+ $linkCache->clear();
+
+ $titleCache = self::getTitleCache();
+ $titleCache->clear();
+ }
+
/**
* Capitalize a text string for a title if it belongs to a namespace that capitalizes
*
@@ -3559,7 +3568,7 @@ class Title {
$urls[] = $this->getInternalUrl( 'action=raw&ctype=text/css' );
}
- wfRunHooks( 'TitleSquidURLs', array( $this, &$urls ) );
+ Hooks::run( 'TitleSquidURLs', array( $this, &$urls ) );
return $urls;
}
@@ -3578,10 +3587,12 @@ class Title {
/**
* Move this page without authentication
*
+ * @deprecated since 1.25 use MovePage class instead
* @param Title $nt The new page Title
* @return array|bool True on success, getUserPermissionsErrors()-like array on failure
*/
public function moveNoAuth( &$nt ) {
+ wfDeprecated( __METHOD__, '1.25' );
return $this->moveTo( $nt, false );
}
@@ -3589,117 +3600,36 @@ class Title {
* Check whether a given move operation would be valid.
* Returns true if ok, or a getUserPermissionsErrors()-like array otherwise
*
- * @todo move this into MovePage
+ * @deprecated since 1.25, use MovePage's methods instead
* @param Title $nt The new title
- * @param bool $auth Indicates whether $wgUser's permissions
- * should be checked
+ * @param bool $auth Whether to check user permissions (uses $wgUser)
* @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;
+ global $wgUser;
- $errors = array();
- if ( !$nt ) {
+ if ( !( $nt instanceof Title ) ) {
// Normally we'd add this to $errors, but we'll get
// lots of syntax errors if $nt is not an object
return array( array( 'badtitletext' ) );
}
- if ( $this->equals( $nt ) ) {
- $errors[] = array( 'selfmove' );
- }
- if ( !$this->isMovable() ) {
- $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
- }
- if ( $nt->isExternal() ) {
- $errors[] = array( 'immobile-target-namespace-iw' );
- }
- if ( !$nt->isMovable() ) {
- $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
- }
-
- $oldid = $this->getArticleID();
- $newid = $nt->getArticleID();
-
- if ( strlen( $nt->getDBkey() ) < 1 ) {
- $errors[] = array( 'articleexists' );
- }
- if (
- ( $this->getDBkey() == '' ) ||
- ( !$oldid ) ||
- ( $nt->getDBkey() == '' )
- ) {
- $errors[] = array( 'badarticleerror' );
- }
-
- // Content model checks
- if ( !$wgContentHandlerUseDB &&
- $this->getContentModel() !== $nt->getContentModel() ) {
- // can't move a page if that would change the page's content model
- $errors[] = array(
- 'bad-target-model',
- ContentHandler::getLocalizedName( $this->getContentModel() ),
- ContentHandler::getLocalizedName( $nt->getContentModel() )
- );
- }
-
- // Image-specific checks
- if ( $this->getNamespace() == NS_FILE ) {
- $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
- }
-
- if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
- $errors[] = array( 'nonfile-cannot-move-to-file' );
- }
+ $mp = new MovePage( $this, $nt );
+ $errors = $mp->isValidMove()->getErrorsArray();
if ( $auth ) {
- $errors = wfMergeErrorArrays( $errors,
- $this->getUserPermissionsErrors( 'move', $wgUser ),
- $this->getUserPermissionsErrors( 'edit', $wgUser ),
- $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
- $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
- }
-
- $match = EditPage::matchSummarySpamRegex( $reason );
- if ( $match !== false ) {
- // This is kind of lame, won't display nice
- $errors[] = array( 'spamprotectiontext' );
- }
-
- $err = null;
- if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
- $errors[] = array( 'hookaborted', $err );
+ $errors = wfMergeErrorArrays(
+ $errors,
+ $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
+ );
}
- # The move is allowed only if (1) the target doesn't exist, or
- # (2) the target is a redirect to the source, and has no history
- # (so we can undo bad moves right after they're done).
-
- if ( 0 != $newid ) { # Target exists; check for validity
- if ( !$this->isValidMoveTarget( $nt ) ) {
- $errors[] = array( 'articleexists' );
- }
- } else {
- $tp = $nt->getTitleProtection();
- $right = $tp['pt_create_perm'];
- if ( $right == 'sysop' ) {
- $right = 'editprotected'; // B/C
- }
- if ( $right == 'autoconfirmed' ) {
- $right = 'editsemiprotected'; // B/C
- }
- if ( $tp and !$wgUser->isAllowed( $right ) ) {
- $errors[] = array( 'cantmove-titleprotected' );
- }
- }
- if ( empty( $errors ) ) {
- return true;
- }
- return $errors;
+ return $errors ? : true;
}
/**
* Check if the requested move target is a valid file move target
+ * @todo move this to MovePage
* @param Title $nt Target title
* @return array List of errors
*/
@@ -3708,29 +3638,11 @@ class Title {
$errors = array();
- // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
-
- $file = wfLocalFile( $this );
- if ( $file->exists() ) {
- if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
- $errors[] = array( 'imageinvalidfilename' );
- }
- if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
- $errors[] = array( 'imagetypemismatch' );
- }
- }
-
- if ( $nt->getNamespace() != NS_FILE ) {
- $errors[] = array( 'imagenocrossnamespace' );
- // From here we want to do checks on a file object, so if we can't
- // create one, we must return.
- return $errors;
- }
-
- // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
-
$destFile = wfLocalFile( $nt );
- if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
+ $destFile->load( File::READ_LATEST );
+ if ( !$wgUser->isAllowed( 'reupload-shared' )
+ && !$destFile->exists() && wfFindFile( $nt )
+ ) {
$errors[] = array( 'file-exists-sharedrepo' );
}
@@ -3740,7 +3652,7 @@ class Title {
/**
* Move a title to a new location
*
- * @todo Deprecate this in favor of MovePage
+ * @deprecated since 1.25, use the MovePage class instead
* @param Title $nt The new title
* @param bool $auth Indicates whether $wgUser's permissions
* should be checked
@@ -3762,8 +3674,6 @@ class Title {
$createRedirect = true;
}
- wfRunHooks( 'TitleMove', array( $this, $nt, $wgUser ) );
-
$mp = new MovePage( $this, $nt );
$status = $mp->move( $wgUser, $reason, $createRedirect );
if ( $status->isOK() ) {
@@ -3899,6 +3809,7 @@ class Title {
* Checks if $this can be moved to a given Title
* - Selects for update, so don't call it unless you mean business
*
+ * @deprecated since 1.25, use MovePage's methods instead
* @param Title $nt The new title to check
* @return bool
*/
@@ -3906,6 +3817,7 @@ class Title {
# Is it an existing file?
if ( $nt->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $nt );
+ $file->load( File::READ_LATEST );
if ( $file->exists() ) {
wfDebug( __METHOD__ . ": file exists\n" );
return false;
@@ -4128,7 +4040,7 @@ class Title {
if ( $this->mIsBigDeletion === null ) {
$dbr = wfGetDB( DB_SLAVE );
- $innerQuery = $dbr->selectSQLText(
+ $revCount = $dbr->selectRowCount(
'revision',
'1',
array( 'rev_page' => $this->getArticleID() ),
@@ -4136,13 +4048,6 @@ class Title {
array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
);
- $revCount = $dbr->query(
- 'SELECT COUNT(*) FROM (' . $innerQuery . ') AS innerQuery',
- __METHOD__
- );
- $revCount = $revCount->fetchRow();
- $revCount = $revCount['COUNT(*)'];
-
$this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
}
@@ -4194,12 +4099,11 @@ class Title {
'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
);
if ( $max !== null ) {
- $res = $dbr->select( 'revision', '1',
+ return $dbr->selectRowCount( 'revision', '1',
$conds,
__METHOD__,
array( 'LIMIT' => $max + 1 ) // extra to detect truncation
);
- return $res->numRows();
} else {
return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
}
@@ -4250,17 +4154,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 === '<' ) ? array() : array( $old->getRawUserText() );
+ return ( $old_cmp === '>' && $new_cmp === '<' ) ?
+ array() :
+ array( $old->getUserText( Revision::RAW ) );
} elseif ( $old->getId() === $new->getParentId() ) {
if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
- $authors[] = $old->getRawUserText();
- if ( $old->getRawUserText() != $new->getRawUserText() ) {
- $authors[] = $new->getRawUserText();
+ $authors[] = $old->getUserText( Revision::RAW );
+ if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
+ $authors[] = $new->getUserText( Revision::RAW );
}
} elseif ( $old_cmp === '>=' ) {
- $authors[] = $old->getRawUserText();
+ $authors[] = $old->getUserText( Revision::RAW );
} elseif ( $new_cmp === '<=' ) {
- $authors[] = $new->getRawUserText();
+ $authors[] = $new->getUserText( Revision::RAW );
}
return $authors;
}
@@ -4334,7 +4240,7 @@ class Title {
*/
public function exists() {
$exists = $this->getArticleID() != 0;
- wfRunHooks( 'TitleExists', array( $this, &$exists ) );
+ Hooks::run( 'TitleExists', array( $this, &$exists ) );
return $exists;
}
@@ -4367,7 +4273,7 @@ class Title {
* @param Title $title
* @param bool|null $isKnown
*/
- wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
+ Hooks::run( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
if ( !is_null( $isKnown ) ) {
return $isKnown;
@@ -4690,7 +4596,7 @@ class Title {
// on the Title object passed in, and should probably
// tell the users to run updateCollations.php --force
// in order to re-sort existing category relations.
- wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
+ Hooks::run( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
if ( $prefix !== '' ) {
# Separate with a line feed, so the unprefixed part is only used as
# a tiebreaker when two pages have the exact same prefix.
@@ -4712,16 +4618,13 @@ class Title {
*/
public function getPageLanguage() {
global $wgLang, $wgLanguageCode;
- wfProfileIn( __METHOD__ );
if ( $this->isSpecialPage() ) {
// special pages are in the user language
- wfProfileOut( __METHOD__ );
return $wgLang;
}
// Checking if DB language is set
if ( $this->mDbPageLanguage ) {
- wfProfileOut( __METHOD__ );
return wfGetLangObj( $this->mDbPageLanguage );
}
@@ -4739,7 +4642,6 @@ class Title {
$langObj = wfGetLangObj( $this->mPageLanguage[0] );
}
- wfProfileOut( __METHOD__ );
return $langObj;
}
@@ -4786,32 +4688,68 @@ class Title {
public function getEditNotices( $oldid = 0 ) {
$notices = array();
- # Optional notices on a per-namespace and per-page basis
+ // Optional notice for the entire namespace
$editnotice_ns = 'editnotice-' . $this->getNamespace();
- $editnotice_ns_message = wfMessage( $editnotice_ns );
- if ( $editnotice_ns_message->exists() ) {
- $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
+ $msg = wfMessage( $editnotice_ns );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ // Edit notices may have complex logic, but output nothing (T91715)
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnotice_ns] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-namespace',
+ Sanitizer::escapeClass( "mw-$editnotice_ns" )
+ ) ),
+ $html
+ );
+ }
}
+
if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
+ // Optional notice for page itself and any parent page
$parts = explode( '/', $this->getDBkey() );
$editnotice_base = $editnotice_ns;
while ( count( $parts ) > 0 ) {
$editnotice_base .= '-' . array_shift( $parts );
- $editnotice_base_msg = wfMessage( $editnotice_base );
- if ( $editnotice_base_msg->exists() ) {
- $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
+ $msg = wfMessage( $editnotice_base );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnotice_base] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-base',
+ Sanitizer::escapeClass( "mw-$editnotice_base" )
+ ) ),
+ $html
+ );
+ }
}
}
} else {
- # Even if there are no subpages in namespace, we still don't want / in MW ns.
+ // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
$editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
- $editnoticeMsg = wfMessage( $editnoticeText );
- if ( $editnoticeMsg->exists() ) {
- $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
+ $msg = wfMessage( $editnoticeText );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnoticeText] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-page',
+ Sanitizer::escapeClass( "mw-$editnoticeText" )
+ ) ),
+ $html
+ );
+ }
}
}
- wfRunHooks( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
+ Hooks::run( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
return $notices;
}
}
diff --git a/includes/TitleArray.php b/includes/TitleArray.php
index b67d9f4d..0fb5b1e9 100644
--- a/includes/TitleArray.php
+++ b/includes/TitleArray.php
@@ -37,7 +37,7 @@ abstract class TitleArray implements Iterator {
*/
static function newFromResult( $res ) {
$array = null;
- if ( !wfRunHooks( 'TitleArrayFromResult', array( &$array, $res ) ) ) {
+ if ( !Hooks::run( 'TitleArrayFromResult', array( &$array, $res ) ) ) {
return null;
}
if ( $array === null ) {
diff --git a/includes/User.php b/includes/User.php
index a925a3c4..3cd69fdc 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -59,6 +59,12 @@ class User implements IDBAccessObject {
const MAX_WATCHED_ITEMS_CACHE = 100;
/**
+ * Exclude user options that are set to their default value.
+ * @since 1.25
+ */
+ const GETOPTIONS_EXCLUDE_DEFAULTS = 1;
+
+ /**
* @var PasswordFactory Lazily loaded factory object for passwords
*/
private static $mPasswordFactory = null;
@@ -96,6 +102,7 @@ class User implements IDBAccessObject {
*/
protected static $mCoreRights = array(
'apihighlimits',
+ 'applychangetags',
'autoconfirmed',
'autopatrol',
'bigdelete',
@@ -103,6 +110,7 @@ class User implements IDBAccessObject {
'blockemail',
'bot',
'browsearchive',
+ 'changetags',
'createaccount',
'createpage',
'createtalk',
@@ -128,6 +136,7 @@ class User implements IDBAccessObject {
'import',
'importupload',
'ipblock-exempt',
+ 'managechangetags',
'markbotedits',
'mergehistory',
'minoredit',
@@ -197,8 +206,10 @@ class User implements IDBAccessObject {
public $mNewpassTime;
public $mEmail;
-
+ /** @var string TS_MW timestamp from the DB */
public $mTouched;
+ /** @var string TS_MW timestamp from cache */
+ protected $mQuickTouched;
protected $mToken;
@@ -288,6 +299,9 @@ class User implements IDBAccessObject {
/** @var array */
private $mWatchedItems = array();
+ /** @var integer User::READ_* constant bitfield used to load data */
+ protected $queryFlagsUsed = self::READ_NORMAL;
+
public static $idCacheByName = array();
/**
@@ -313,104 +327,144 @@ class User implements IDBAccessObject {
/**
* Load the user table data for this object from the source given by mFrom.
+ *
+ * @param integer $flags User::READ_* constant bitfield
*/
- public function load() {
+ public function load( $flags = self::READ_LATEST ) {
if ( $this->mLoadedItems === true ) {
return;
}
- wfProfileIn( __METHOD__ );
// Set it now to avoid infinite recursion in accessors
$this->mLoadedItems = true;
+ $this->queryFlagsUsed = $flags;
switch ( $this->mFrom ) {
case 'defaults':
$this->loadDefaults();
break;
case 'name':
+ // @TODO: this gets the ID from a slave, assuming renames
+ // are rare. This should be controllable and more consistent.
$this->mId = self::idFromName( $this->mName );
if ( !$this->mId ) {
// Nonexistent user placeholder object
$this->loadDefaults( $this->mName );
} else {
- $this->loadFromId();
+ $this->loadFromId( $flags );
}
break;
case 'id':
- $this->loadFromId();
+ $this->loadFromId( $flags );
break;
case 'session':
if ( !$this->loadFromSession() ) {
// Loading from session failed. Load defaults.
$this->loadDefaults();
}
- wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) );
+ Hooks::run( 'UserLoadAfterLoadFromSession', array( $this ) );
break;
default:
- wfProfileOut( __METHOD__ );
throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
}
- wfProfileOut( __METHOD__ );
}
/**
* Load user table data, given mId has already been set.
+ * @param integer $flags User::READ_* constant bitfield
* @return bool False if the ID does not exist, true otherwise
*/
- public function loadFromId() {
- global $wgMemc;
+ public function loadFromId( $flags = self::READ_LATEST ) {
if ( $this->mId == 0 ) {
$this->loadDefaults();
return false;
}
// Try cache
- $key = wfMemcKey( 'user', 'id', $this->mId );
- $data = $wgMemc->get( $key );
- if ( !is_array( $data ) || $data['mVersion'] != self::VERSION ) {
- // Object is expired, load from DB
- $data = false;
- }
-
- if ( !$data ) {
+ $cache = $this->loadFromCache();
+ if ( !$cache ) {
wfDebug( "User: cache miss for user {$this->mId}\n" );
// Load from DB
- if ( !$this->loadFromDatabase() ) {
+ if ( !$this->loadFromDatabase( $flags ) ) {
// Can't load from ID, user is anonymous
return false;
}
- $this->saveToCache();
- } else {
- wfDebug( "User: got user {$this->mId} from cache\n" );
- // Restore from cache
- foreach ( self::$mCacheVars as $name ) {
- $this->$name = $data[$name];
+ if ( $flags & self::READ_LATEST ) {
+ // Only save master data back to the cache to keep it consistent.
+ // @TODO: save it anyway and have callers specifiy $flags and have
+ // load() called as needed. That requires updating MANY callers...
+ $this->saveToCache();
}
}
$this->mLoadedItems = true;
+ $this->queryFlagsUsed = $flags;
+
+ return true;
+ }
+
+ /**
+ * Load user data from shared cache, given mId has already been set.
+ *
+ * @return bool false if the ID does not exist or data is invalid, true otherwise
+ * @since 1.25
+ */
+ protected function loadFromCache() {
+ global $wgMemc;
+
+ if ( $this->mId == 0 ) {
+ $this->loadDefaults();
+ return false;
+ }
+
+ $key = wfMemcKey( 'user', 'id', $this->mId );
+ $data = $wgMemc->get( $key );
+ if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
+ // Object is expired
+ return false;
+ }
+
+ wfDebug( "User: got user {$this->mId} from cache\n" );
+
+ // Restore from cache
+ foreach ( self::$mCacheVars as $name ) {
+ $this->$name = $data[$name];
+ }
return true;
}
/**
* Save user data to the shared cache
+ *
+ * This method should not be called outside the User class
*/
public function saveToCache() {
+ global $wgMemc;
+
$this->load();
$this->loadGroups();
$this->loadOptions();
+
if ( $this->isAnon() ) {
// Anonymous users are uncached
return;
}
+
+ // The cache needs good consistency due to its high TTL, so the user
+ // should have been loaded from the master to avoid lag amplification.
+ if ( !( $this->queryFlagsUsed & self::READ_LATEST ) ) {
+ wfWarn( "Cannot cache slave-loaded User object with ID '{$this->mId}'." );
+ return;
+ }
+
$data = array();
foreach ( self::$mCacheVars as $name ) {
$data[$name] = $this->$name;
}
$data['mVersion'] = self::VERSION;
$key = wfMemcKey( 'user', 'id', $this->mId );
- global $wgMemc;
+
$wgMemc->set( $key, $data );
}
@@ -624,10 +678,11 @@ class User implements IDBAccessObject {
global $wgContLang, $wgMaxNameChars;
if ( $name == ''
- || User::isIP( $name )
- || strpos( $name, '/' ) !== false
- || strlen( $name ) > $wgMaxNameChars
- || $name != $wgContLang->ucfirst( $name ) ) {
+ || User::isIP( $name )
+ || strpos( $name, '/' ) !== false
+ || strlen( $name ) > $wgMaxNameChars
+ || $name != $wgContLang->ucfirst( $name )
+ ) {
wfDebugLog( 'username', __METHOD__ .
": '$name' invalid due to empty, IP, slash, length, or lowercase" );
return false;
@@ -684,7 +739,7 @@ class User implements IDBAccessObject {
static $reservedUsernames = false;
if ( !$reservedUsernames ) {
$reservedUsernames = $wgReservedUsernames;
- wfRunHooks( 'UserGetReservedNames', array( &$reservedUsernames ) );
+ Hooks::run( 'UserGetReservedNames', array( &$reservedUsernames ) );
}
// Certain names may be reserved for batch processes.
@@ -801,7 +856,7 @@ class User implements IDBAccessObject {
$result = false; //init $result to false for the internal checks
- if ( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) ) {
+ if ( !Hooks::run( 'isValidPassword', array( $password, &$result, $this ) ) ) {
$status->error( $result );
return $status;
}
@@ -867,7 +922,7 @@ class User implements IDBAccessObject {
);
}
// Give extensions a chance to force an expiration
- wfRunHooks( 'ResetPasswordExpiration', array( $this, &$newExpire ) );
+ Hooks::run( 'ResetPasswordExpiration', array( $this, &$newExpire ) );
$this->mPasswordExpires = $newExpire;
}
@@ -1007,7 +1062,6 @@ class User implements IDBAccessObject {
* @param string|bool $name
*/
public function loadDefaults( $name = false ) {
- wfProfileIn( __METHOD__ );
$passwordFactory = self::getPasswordFactory();
@@ -1037,9 +1091,7 @@ class User implements IDBAccessObject {
$this->mRegistration = wfTimestamp( TS_MW );
$this->mGroups = array();
- wfRunHooks( 'UserLoadDefaults', array( $this, $name ) );
-
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'UserLoadDefaults', array( $this, $name ) );
}
/**
@@ -1072,11 +1124,12 @@ class User implements IDBAccessObject {
/**
* Load user data from the session or login cookie.
+ *
* @return bool True if the user is logged in, false otherwise.
*/
private function loadFromSession() {
$result = null;
- wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) );
+ Hooks::run( 'UserLoadFromSession', array( $this, &$result ) );
if ( $result !== null ) {
return $result;
}
@@ -1110,6 +1163,7 @@ class User implements IDBAccessObject {
}
$proposedUser = User::newFromId( $sId );
+ $proposedUser->load( self::READ_LATEST );
if ( !$proposedUser->isLoggedIn() ) {
// Not a valid ID
return false;
@@ -1154,10 +1208,10 @@ class User implements IDBAccessObject {
* 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
+ * @param integer $flags User::READ_* constant bitfield
* @return bool True if the user exists, false if the user is anonymous
*/
- public function loadFromDatabase( $flags = 0 ) {
+ public function loadFromDatabase( $flags = self::READ_LATEST ) {
// Paranoia
$this->mId = intval( $this->mId );
@@ -1167,8 +1221,11 @@ class User implements IDBAccessObject {
return false;
}
- $dbr = wfGetDB( DB_MASTER );
- $s = $dbr->selectRow(
+ $db = ( $flags & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+
+ $s = $db->selectRow(
'user',
self::selectFields(),
array( 'user_id' => $this->mId ),
@@ -1178,7 +1235,8 @@ class User implements IDBAccessObject {
: array()
);
- wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
+ $this->queryFlagsUsed = $flags;
+ Hooks::run( 'UserLoadFromDatabase', array( $this, &$s ) );
if ( $s !== false ) {
// Initialise user table data
@@ -1203,7 +1261,7 @@ class User implements IDBAccessObject {
* user_groups Array with groups out of the user_groups table
* user_properties Array with properties out of the user_properties table
*/
- public function loadFromRow( $row, $data = null ) {
+ protected function loadFromRow( $row, $data = null ) {
$all = true;
$passwordFactory = self::getPasswordFactory();
@@ -1232,6 +1290,10 @@ class User implements IDBAccessObject {
$all = false;
}
+ if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
+ self::$idCacheByName[$row->user_name] = $row->user_id;
+ }
+
if ( isset( $row->user_editcount ) ) {
$this->mEditCount = $row->user_editcount;
} else {
@@ -1311,8 +1373,10 @@ class User implements IDBAccessObject {
*/
private function loadGroups() {
if ( is_null( $this->mGroups ) ) {
- $dbr = wfGetDB( DB_MASTER );
- $res = $dbr->select( 'user_groups',
+ $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+ $res = $db->select( 'user_groups',
array( 'ug_group' ),
array( 'ug_user' => $this->mId ),
__METHOD__ );
@@ -1333,13 +1397,20 @@ class User implements IDBAccessObject {
* @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__
- ) );
+ if ( $this->getId() !== 0 &&
+ ( $this->mPassword === null || $this->mNewpassword === null )
+ ) {
+ $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+
+ $this->loadFromRow( $db->selectRow(
+ 'user',
+ array( 'user_password', 'user_newpassword',
+ 'user_newpass_time', 'user_password_expires' ),
+ array( 'user_id' => $this->getId() ),
+ __METHOD__
+ ) );
}
}
@@ -1361,7 +1432,7 @@ class User implements IDBAccessObject {
global $wgAutopromoteOnceLogInRC, $wgAuth;
$toPromote = array();
- if ( $this->getId() ) {
+ if ( !wfReadOnly() && $this->getId() ) {
$toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
if ( count( $toPromote ) ) {
$oldGroups = $this->getGroups(); // previous groups
@@ -1387,6 +1458,7 @@ class User implements IDBAccessObject {
}
}
}
+
return $toPromote;
}
@@ -1444,7 +1516,7 @@ class User implements IDBAccessObject {
}
$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
- wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
+ Hooks::run( 'UserGetDefaultOptions', array( &$defOpt ) );
return $defOpt;
}
@@ -1477,7 +1549,6 @@ class User implements IDBAccessObject {
return;
}
- wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . ": checking...\n" );
// Initialize data...
@@ -1550,9 +1621,8 @@ class User implements IDBAccessObject {
}
// Extensions
- wfRunHooks( 'GetBlockedStatus', array( &$this ) );
+ Hooks::run( 'GetBlockedStatus', array( &$this ) );
- wfProfileOut( __METHOD__ );
}
/**
@@ -1584,7 +1654,6 @@ class User implements IDBAccessObject {
* @return bool True if blacklisted.
*/
public function inDnsBlacklist( $ip, $bases ) {
- wfProfileIn( __METHOD__ );
$found = false;
// @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
@@ -1619,7 +1688,6 @@ class User implements IDBAccessObject {
}
}
- wfProfileOut( __METHOD__ );
return $found;
}
@@ -1636,7 +1704,6 @@ class User implements IDBAccessObject {
if ( !$wgProxyList ) {
return false;
}
- wfProfileIn( __METHOD__ );
if ( !is_array( $wgProxyList ) ) {
// Load from the specified file
@@ -1653,7 +1720,6 @@ class User implements IDBAccessObject {
} else {
$ret = false;
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -1690,7 +1756,7 @@ class User implements IDBAccessObject {
public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
// Call the 'PingLimiter' hook
$result = false;
- if ( !wfRunHooks( 'PingLimiter', array( &$this, $action, &$result, $incrBy ) ) ) {
+ if ( !Hooks::run( 'PingLimiter', array( &$this, $action, &$result, $incrBy ) ) ) {
return $result;
}
@@ -1705,8 +1771,6 @@ class User implements IDBAccessObject {
}
global $wgMemc;
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $action );
$limits = $wgRateLimits[$action];
$keys = array();
@@ -1748,7 +1812,9 @@ class User implements IDBAccessObject {
// If more than one group applies, use the group with the highest limit
foreach ( $this->getGroups() as $group ) {
if ( isset( $limits[$group] ) ) {
- if ( $userLimit === false || $limits[$group] > $userLimit ) {
+ if ( $userLimit === false
+ || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
+ ) {
$userLimit = $limits[$group];
}
}
@@ -1785,8 +1851,6 @@ class User implements IDBAccessObject {
}
}
- wfProfileOut( __METHOD__ . '-' . $action );
- wfProfileOut( __METHOD__ );
return $triggered;
}
@@ -1821,7 +1885,6 @@ class User implements IDBAccessObject {
*/
public function isBlockedFrom( $title, $bFromSlave = false ) {
global $wgBlockAllowsUTEdit;
- wfProfileIn( __METHOD__ );
$blocked = $this->isBlocked( $bFromSlave );
$allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
@@ -1832,9 +1895,8 @@ class User implements IDBAccessObject {
wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
}
- wfRunHooks( 'UserIsBlockedFrom', array( $this, $title, &$blocked, &$allowUsertalk ) );
+ Hooks::run( 'UserIsBlockedFrom', array( $this, $title, &$blocked, &$allowUsertalk ) );
- wfProfileOut( __METHOD__ );
return $blocked;
}
@@ -1884,7 +1946,7 @@ class User implements IDBAccessObject {
$ip = $this->getRequest()->getIP();
}
$blocked = false;
- wfRunHooks( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) );
+ Hooks::run( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) );
$this->mBlockedGlobally = (bool)$blocked;
return $this->mBlockedGlobally;
}
@@ -2009,17 +2071,7 @@ class User implements IDBAccessObject {
// Anon newtalk disabled by configuration.
$this->mNewtalk = false;
} else {
- global $wgMemc;
- $key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
- $newtalk = $wgMemc->get( $key );
- if ( strval( $newtalk ) !== '' ) {
- $this->mNewtalk = (bool)$newtalk;
- } else {
- // Since we are caching this, make sure it is up to date by getting it
- // from the master
- $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true );
- $wgMemc->set( $key, (int)$this->mNewtalk, 1800 );
- }
+ $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
}
} else {
$this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
@@ -2044,7 +2096,7 @@ class User implements IDBAccessObject {
*/
public function getNewMessageLinks() {
$talks = array();
- if ( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
+ if ( !Hooks::run( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
return $talks;
} elseif ( !$this->getNewtalk() ) {
return array();
@@ -2089,17 +2141,13 @@ class User implements IDBAccessObject {
* @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
* @return bool True if the user has new messages
*/
- protected function checkNewtalk( $field, $id, $fromMaster = false ) {
- if ( $fromMaster ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_SLAVE );
- }
- $ok = $db->selectField( 'user_newtalk', $field,
- array( $field => $id ), __METHOD__ );
+ protected function checkNewtalk( $field, $id ) {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $ok = $dbr->selectField( 'user_newtalk', $field, array( $field => $id ), __METHOD__ );
+
return $ok !== false;
}
@@ -2194,9 +2242,15 @@ class User implements IDBAccessObject {
* user_touched field when we update things.
* @return string Timestamp in TS_MW format
*/
- private static function newTouchedTimestamp() {
+ private function newTouchedTimestamp() {
global $wgClockSkewFudge;
- return wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
+
+ $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
+ if ( $this->mTouched && $time <= $this->mTouched ) {
+ $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
+ }
+
+ return $time;
}
/**
@@ -2207,9 +2261,10 @@ class User implements IDBAccessObject {
* Called implicitly from invalidateCache() and saveSettings().
*/
public function clearSharedCache() {
+ global $wgMemc;
+
$this->load();
if ( $this->mId ) {
- global $wgMemc;
$wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) );
}
}
@@ -2225,7 +2280,7 @@ class User implements IDBAccessObject {
}
$this->load();
if ( $this->mId ) {
- $this->mTouched = self::newTouchedTimestamp();
+ $this->mTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
$userid = $this->mId;
@@ -2249,21 +2304,63 @@ class User implements IDBAccessObject {
}
/**
+ * Update the "touched" timestamp for the user
+ *
+ * This is useful on various login/logout events when making sure that
+ * a browser or proxy that has multiple tenants does not suffer cache
+ * pollution where the new user sees the old users content. The value
+ * of getTouched() is checked when determining 304 vs 200 responses.
+ * Unlike invalidateCache(), this preserves the User object cache and
+ * avoids database writes.
+ *
+ * @since 1.25
+ */
+ public function touch() {
+ global $wgMemc;
+
+ $this->load();
+
+ if ( $this->mId ) {
+ $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
+ $timestamp = $this->newTouchedTimestamp();
+ $wgMemc->set( $key, $timestamp );
+ $this->mQuickTouched = $timestamp;
+ }
+ }
+
+ /**
* Validate the cache for this account.
* @param string $timestamp A timestamp in TS_MW format
* @return bool
*/
public function validateCache( $timestamp ) {
- $this->load();
- return ( $timestamp >= $this->mTouched );
+ return ( $timestamp >= $this->getTouched() );
}
/**
* Get the user touched timestamp
- * @return string Timestamp
+ * @return string TS_MW Timestamp
*/
public function getTouched() {
+ global $wgMemc;
+
$this->load();
+
+ if ( $this->mId ) {
+ if ( $this->mQuickTouched === null ) {
+ $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
+ $timestamp = $wgMemc->get( $key );
+ if ( $timestamp ) {
+ $this->mQuickTouched = $timestamp;
+ } else {
+ # Set the timestamp to get HTTP 304 cache hits
+ $this->touch();
+ }
+ }
+
+ return max( $this->mTouched, $this->mQuickTouched );
+ }
+
return $this->mTouched;
}
@@ -2339,11 +2436,7 @@ class User implements IDBAccessObject {
$this->setToken();
$passwordFactory = self::getPasswordFactory();
- if ( $str === null ) {
- $this->mPassword = $passwordFactory->newFromCiphertext( null );
- } else {
- $this->mPassword = $passwordFactory->newFromPlaintext( $str );
- }
+ $this->mPassword = $passwordFactory->newFromPlaintext( $str );
$this->mNewpassword = $passwordFactory->newFromCiphertext( null );
$this->mNewpassTime = null;
@@ -2388,14 +2481,11 @@ class User implements IDBAccessObject {
public function setNewpassword( $str, $throttle = true ) {
$this->loadPasswords();
+ $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str );
if ( $str === null ) {
- $this->mNewpassword = '';
$this->mNewpassTime = null;
- } else {
- $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str );
- if ( $throttle ) {
- $this->mNewpassTime = wfTimestampNow();
- }
+ } elseif ( $throttle ) {
+ $this->mNewpassTime = wfTimestampNow();
}
}
@@ -2420,7 +2510,7 @@ class User implements IDBAccessObject {
*/
public function getEmail() {
$this->load();
- wfRunHooks( 'UserGetEmail', array( $this, &$this->mEmail ) );
+ Hooks::run( 'UserGetEmail', array( $this, &$this->mEmail ) );
return $this->mEmail;
}
@@ -2430,7 +2520,7 @@ class User implements IDBAccessObject {
*/
public function getEmailAuthenticationTimestamp() {
$this->load();
- wfRunHooks( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
+ Hooks::run( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
return $this->mEmailAuthenticated;
}
@@ -2445,7 +2535,7 @@ class User implements IDBAccessObject {
}
$this->invalidateEmail();
$this->mEmail = $str;
- wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
+ Hooks::run( 'UserSetEmail', array( $this, &$this->mEmail ) );
}
/**
@@ -2474,7 +2564,7 @@ class User implements IDBAccessObject {
$type = $oldaddr != '' ? 'changed' : 'set';
$result = $this->sendConfirmationMail( $type );
if ( $result->isGood() ) {
- // Say the the caller that a confirmation mail has been sent
+ // Say to the caller that a confirmation mail has been sent
$result->value = 'eauth';
}
} else {
@@ -2538,9 +2628,12 @@ class User implements IDBAccessObject {
/**
* Get all user's options
*
+ * @param int $flags Bitwise combination of:
+ * User::GETOPTIONS_EXCLUDE_DEFAULTS Exclude user options that are set
+ * to the default value. (Since 1.25)
* @return array
*/
- public function getOptions() {
+ public function getOptions( $flags = 0 ) {
global $wgHiddenPrefs;
$this->loadOptions();
$options = $this->mOptions;
@@ -2557,6 +2650,10 @@ class User implements IDBAccessObject {
}
}
+ if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
+ $options = array_diff_assoc( $options, self::getDefaultOptions() );
+ }
+
return $options;
}
@@ -2624,7 +2721,9 @@ class User implements IDBAccessObject {
$token = $this->getOption( $oname );
if ( !$token ) {
$token = $this->resetTokenFromOption( $oname );
- $this->saveSettings();
+ if ( !wfReadOnly() ) {
+ $this->saveSettings();
+ }
}
return $token;
}
@@ -2814,7 +2913,7 @@ class User implements IDBAccessObject {
}
}
- wfRunHooks( 'UserResetAllOptions', array( $this, &$newOptions, $this->mOptions, $resetKinds ) );
+ Hooks::run( 'UserResetAllOptions', array( $this, &$newOptions, $this->mOptions, $resetKinds ) );
$this->mOptions = $newOptions;
$this->mOptionsLoaded = true;
@@ -2850,7 +2949,7 @@ class User implements IDBAccessObject {
return false;
} else {
$https = $this->getBoolOption( 'prefershttps' );
- wfRunHooks( 'UserRequiresHTTPS', array( $this, &$https ) );
+ Hooks::run( 'UserRequiresHTTPS', array( $this, &$https ) );
if ( $https ) {
$https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
}
@@ -2881,7 +2980,7 @@ class User implements IDBAccessObject {
public function getRights() {
if ( is_null( $this->mRights ) ) {
$this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
- wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) );
+ Hooks::run( 'UserGetRights', array( $this, &$this->mRights ) );
// Force reindexation of rights when a hook has unset one of them
$this->mRights = array_values( array_unique( $this->mRights ) );
}
@@ -2908,16 +3007,14 @@ class User implements IDBAccessObject {
*/
public function getEffectiveGroups( $recache = false ) {
if ( $recache || is_null( $this->mEffectiveGroups ) ) {
- wfProfileIn( __METHOD__ );
$this->mEffectiveGroups = array_unique( array_merge(
$this->getGroups(), // explicit groups
$this->getAutomaticGroups( $recache ) // implicit groups
) );
// Hook for additional groups
- wfRunHooks( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) );
+ Hooks::run( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) );
// Force reindexation of groups when a hook has unset one of them
$this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
- wfProfileOut( __METHOD__ );
}
return $this->mEffectiveGroups;
}
@@ -2931,7 +3028,6 @@ class User implements IDBAccessObject {
*/
public function getAutomaticGroups( $recache = false ) {
if ( $recache || is_null( $this->mImplicitGroups ) ) {
- wfProfileIn( __METHOD__ );
$this->mImplicitGroups = array( '*' );
if ( $this->getId() ) {
$this->mImplicitGroups[] = 'user';
@@ -2946,7 +3042,6 @@ class User implements IDBAccessObject {
// as getEffectiveGroups() depends on this function
$this->mEffectiveGroups = null;
}
- wfProfileOut( __METHOD__ );
}
return $this->mImplicitGroups;
}
@@ -2961,9 +3056,13 @@ class User implements IDBAccessObject {
* @return array Names of the groups the user has belonged to.
*/
public function getFormerGroups() {
+ $this->load();
+
if ( is_null( $this->mFormerGroups ) ) {
- $dbr = wfGetDB( DB_MASTER );
- $res = $dbr->select( 'user_former_groups',
+ $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+ $res = $db->select( 'user_former_groups',
array( 'ufg_group' ),
array( 'ufg_user' => $this->mId ),
__METHOD__ );
@@ -2972,6 +3071,7 @@ class User implements IDBAccessObject {
$this->mFormerGroups[] = $row->ufg_group;
}
}
+
return $this->mFormerGroups;
}
@@ -2986,7 +3086,6 @@ class User implements IDBAccessObject {
if ( $this->mEditCount === null ) {
/* Populate the count, if it has not been populated yet */
- wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
// check if the user_editcount field has been initialized
$count = $dbr->selectField(
@@ -3000,7 +3099,6 @@ class User implements IDBAccessObject {
$count = $this->initEditCount();
}
$this->mEditCount = $count;
- wfProfileOut( __METHOD__ );
}
return (int)$this->mEditCount;
}
@@ -3009,20 +3107,26 @@ class User implements IDBAccessObject {
* Add the user to the given group.
* This takes immediate effect.
* @param string $group Name of the group to add
+ * @return bool
*/
public function addGroup( $group ) {
- if ( wfRunHooks( 'UserAddGroup', array( $this, &$group ) ) ) {
- $dbw = wfGetDB( DB_MASTER );
- if ( $this->getId() ) {
- $dbw->insert( 'user_groups',
- array(
- 'ug_user' => $this->getID(),
- 'ug_group' => $group,
- ),
- __METHOD__,
- array( 'IGNORE' ) );
- }
+ $this->load();
+
+ if ( !Hooks::run( 'UserAddGroup', array( $this, &$group ) ) ) {
+ return false;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ if ( $this->getId() ) {
+ $dbw->insert( 'user_groups',
+ array(
+ 'ug_user' => $this->getID(),
+ 'ug_group' => $group,
+ ),
+ __METHOD__,
+ array( 'IGNORE' ) );
}
+
$this->loadGroups();
$this->mGroups[] = $group;
// In case loadGroups was not called before, we now have the right twice.
@@ -3035,31 +3139,39 @@ class User implements IDBAccessObject {
$this->mRights = null;
$this->invalidateCache();
+
+ return true;
}
/**
* Remove the user from the given group.
* This takes immediate effect.
* @param string $group Name of the group to remove
+ * @return bool
*/
public function removeGroup( $group ) {
$this->load();
- if ( wfRunHooks( 'UserRemoveGroup', array( $this, &$group ) ) ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'user_groups',
- array(
- 'ug_user' => $this->getID(),
- 'ug_group' => $group,
- ), __METHOD__ );
- // Remember that the user was in this group
- $dbw->insert( 'user_former_groups',
- array(
- 'ufg_user' => $this->getID(),
- 'ufg_group' => $group,
- ),
- __METHOD__,
- array( 'IGNORE' ) );
+ if ( !Hooks::run( 'UserRemoveGroup', array( $this, &$group ) ) ) {
+ return false;
}
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'user_groups',
+ array(
+ 'ug_user' => $this->getID(),
+ 'ug_group' => $group,
+ ), __METHOD__
+ );
+ // Remember that the user was in this group
+ $dbw->insert( 'user_former_groups',
+ array(
+ 'ufg_user' => $this->getID(),
+ 'ufg_group' => $group,
+ ),
+ __METHOD__,
+ array( 'IGNORE' )
+ );
+
$this->loadGroups();
$this->mGroups = array_diff( $this->mGroups, array( $group ) );
@@ -3069,6 +3181,8 @@ class User implements IDBAccessObject {
$this->mRights = null;
$this->invalidateCache();
+
+ return true;
}
/**
@@ -3268,7 +3382,7 @@ class User implements IDBAccessObject {
// 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 ) ) ) {
+ if ( !Hooks::run( 'UserClearNewTalkNotification', array( &$this, $oldid ) ) ) {
return;
}
@@ -3354,10 +3468,17 @@ class User implements IDBAccessObject {
* false: Force NOT setting the secure attribute when setting the cookie
* null (default): Use the default ($wgCookieSecure) to set the secure attribute
* @param array $params Array of options sent passed to WebResponse::setcookie()
+ * @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null
+ * is passed.
*/
- protected function setCookie( $name, $value, $exp = 0, $secure = null, $params = array() ) {
+ protected function setCookie(
+ $name, $value, $exp = 0, $secure = null, $params = array(), $request = null
+ ) {
+ if ( $request === null ) {
+ $request = $this->getRequest();
+ }
$params['secure'] = $secure;
- $this->getRequest()->response()->setcookie( $name, $value, $exp, $params );
+ $request->response()->setcookie( $name, $value, $exp, $params );
}
/**
@@ -3396,7 +3517,9 @@ class User implements IDBAccessObject {
// Simply by setting every cell in the user_token column to NULL and letting them be
// regenerated as users log back into the wiki.
$this->setToken();
- $this->saveSettings();
+ if ( !wfReadOnly() ) {
+ $this->saveSettings();
+ }
}
$session = array(
'wsUserID' => $this->mId,
@@ -3413,7 +3536,7 @@ class User implements IDBAccessObject {
$cookies['Token'] = false;
}
- wfRunHooks( 'UserSetCookies', array( $this, &$session, &$cookies ) );
+ Hooks::run( 'UserSetCookies', array( $this, &$session, &$cookies ) );
foreach ( $session as $name => $value ) {
$request->setSessionData( $name, $value );
@@ -3422,7 +3545,7 @@ class User implements IDBAccessObject {
if ( $value === false ) {
$this->clearCookie( $name );
} else {
- $this->setCookie( $name, $value, 0, $secure );
+ $this->setCookie( $name, $value, 0, $secure, array(), $request );
}
}
@@ -3448,7 +3571,7 @@ class User implements IDBAccessObject {
* Log this user out.
*/
public function logout() {
- if ( wfRunHooks( 'UserLogout', array( &$this ) ) ) {
+ if ( Hooks::run( 'UserLogout', array( &$this ) ) ) {
$this->doLogout();
}
}
@@ -3477,16 +3600,34 @@ class User implements IDBAccessObject {
public function saveSettings() {
global $wgAuth;
- $this->load();
- $this->loadPasswords();
if ( wfReadOnly() ) {
+ // @TODO: caller should deal with this instead!
+ // This should really just be an exception.
+ MWExceptionHandler::logException( new DBExpectedError(
+ null,
+ "Could not update user with ID '{$this->mId}'; DB is read-only."
+ ) );
return;
}
+
+ $this->load();
+ $this->loadPasswords();
if ( 0 == $this->mId ) {
- return;
+ return; // anon
}
- $this->mTouched = self::newTouchedTimestamp();
+ // This method is for updating existing users, so the user should
+ // have been loaded from the master to begin with to avoid problems.
+ if ( !( $this->queryFlagsUsed & self::READ_LATEST ) ) {
+ wfWarn( "Attempting to save slave-loaded User object with ID '{$this->mId}'." );
+ }
+
+ // Get a new user_touched that is higher than the old one.
+ // This will be used for a CAS check as a last-resort safety
+ // check against race conditions and slave lag.
+ $oldTouched = $this->mTouched;
+ $this->mTouched = $this->newTouchedTimestamp();
+
if ( !$wgAuth->allowSetLocalPassword() ) {
$this->mPassword = self::getPasswordFactory()->newFromCiphertext( null );
}
@@ -3507,13 +3648,25 @@ class User implements IDBAccessObject {
'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
'user_password_expires' => $dbw->timestampOrNull( $this->mPasswordExpires ),
), array( /* WHERE */
- 'user_id' => $this->mId
+ 'user_id' => $this->mId,
+ 'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
), __METHOD__
);
+ if ( !$dbw->affectedRows() ) {
+ // User was changed in the meantime or loaded with stale data
+ MWExceptionHandler::logException( new MWException(
+ "CAS update failed on user_touched for user ID '{$this->mId}'."
+ ) );
+ // Maybe the problem was a missed cache update; clear it to be safe
+ $this->clearSharedCache();
+
+ return;
+ }
+
$this->saveOptions();
- wfRunHooks( 'UserSaveSettings', array( $this ) );
+ Hooks::run( 'UserSaveSettings', array( $this ) );
$this->clearSharedCache();
$this->getUserPage()->invalidateCache();
}
@@ -3579,7 +3732,7 @@ class User implements IDBAccessObject {
'user_token' => strval( $user->mToken ),
'user_registration' => $dbw->timestamp( $user->mRegistration ),
'user_editcount' => 0,
- 'user_touched' => $dbw->timestamp( self::newTouchedTimestamp() ),
+ 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
);
foreach ( $params as $name => $value ) {
$fields["user_$name"] = $value;
@@ -3626,7 +3779,7 @@ class User implements IDBAccessObject {
$this->setToken(); // init token
}
- $this->mTouched = self::newTouchedTimestamp();
+ $this->mTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
$inWrite = $dbw->writesOrCallbacksPending();
@@ -3661,7 +3814,7 @@ class User implements IDBAccessObject {
// using CentralAuth. It's should be OK to commit and break the snapshot.
$dbw->commit( __METHOD__, 'flush' );
$options = array();
- $flags = 0;
+ $flags = self::READ_LATEST;
}
$this->mId = $dbw->selectField( 'user', 'user_id',
array( 'user_name' => $this->mName ), __METHOD__, $options );
@@ -3793,8 +3946,6 @@ class User implements IDBAccessObject {
public function checkPassword( $password ) {
global $wgAuth, $wgLegacyEncoding;
- $section = new ProfileSection( __METHOD__ );
-
$this->loadPasswords();
// Some passwords will give a fatal Status, which means there is
@@ -3817,7 +3968,6 @@ class User implements IDBAccessObject {
return false;
}
- $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
@@ -3831,7 +3981,8 @@ class User implements IDBAccessObject {
}
}
- if ( $passwordFactory->needsUpdate( $this->mPassword ) ) {
+ $passwordFactory = self::getPasswordFactory();
+ if ( $passwordFactory->needsUpdate( $this->mPassword ) && !wfReadOnly() ) {
$this->mPassword = $passwordFactory->newFromPlaintext( $password );
$this->saveSettings();
}
@@ -3877,22 +4028,15 @@ class User implements IDBAccessObject {
}
/**
- * Initialize (if necessary) and return a session token value
- * which can be used in edit forms to show that the user's
- * login credentials aren't being hijacked with a foreign form
- * submission.
- *
- * @since 1.19
+ * Internal implementation for self::getEditToken() and
+ * self::matchEditToken().
*
- * @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
+ * @param string|array $salt
+ * @param WebRequest $request
+ * @param string|int $timestamp
+ * @return string
*/
- public function getEditToken( $salt = '', $request = null ) {
- if ( $request == null ) {
- $request = $this->getRequest();
- }
-
+ private function getEditTokenAtTimestamp( $salt, $request, $timestamp ) {
if ( $this->isAnon() ) {
return self::EDIT_TOKEN_SUFFIX;
} else {
@@ -3904,11 +4048,31 @@ class User implements IDBAccessObject {
if ( is_array( $salt ) ) {
$salt = implode( '|', $salt );
}
- return md5( $token . $salt ) . self::EDIT_TOKEN_SUFFIX;
+ return hash_hmac( 'md5', $timestamp . $salt, $token, false ) .
+ dechex( $timestamp ) .
+ self::EDIT_TOKEN_SUFFIX;
}
}
/**
+ * Initialize (if necessary) and return a session token value
+ * which can be used in edit forms to show that the user's
+ * login credentials aren't being hijacked with a foreign form
+ * submission.
+ *
+ * @since 1.19
+ *
+ * @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 ) {
+ return $this->getEditTokenAtTimestamp(
+ $salt, $request ?: $this->getRequest(), wfTimestamp()
+ );
+ }
+
+ /**
* Generate a looking random token for various uses.
*
* @return string The new random token
@@ -3920,6 +4084,20 @@ class User implements IDBAccessObject {
}
/**
+ * Get the embedded timestamp from a token.
+ * @param string $val Input token
+ * @return int|null
+ */
+ public static function getEditTokenTimestamp( $val ) {
+ $suffixLen = strlen( self::EDIT_TOKEN_SUFFIX );
+ if ( strlen( $val ) <= 32 + $suffixLen ) {
+ return null;
+ }
+
+ return hexdec( substr( $val, 32, -$suffixLen ) );
+ }
+
+ /**
* Check given value against the token value stored in the session.
* A match should confirm that the form was submitted from the
* user's own login session, not a form submission from a third-party
@@ -3928,15 +4106,32 @@ class User implements IDBAccessObject {
* @param string $val Input value to compare
* @param string $salt Optional function-specific data for hashing
* @param WebRequest|null $request Object to use or null to use $wgRequest
+ * @param int $maxage Fail tokens older than this, in seconds
* @return bool Whether the token matches
*/
- public function matchEditToken( $val, $salt = '', $request = null ) {
- $sessionToken = $this->getEditToken( $salt, $request );
+ public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
+ if ( $this->isAnon() ) {
+ return $val === self::EDIT_TOKEN_SUFFIX;
+ }
+
+ $timestamp = self::getEditTokenTimestamp( $val );
+ if ( $timestamp === null ) {
+ return false;
+ }
+ if ( $maxage !== null && $timestamp < wfTimestamp() - $maxage ) {
+ // Expired token
+ return false;
+ }
+
+ $sessionToken = $this->getEditTokenAtTimestamp(
+ $salt, $request ?: $this->getRequest(), $timestamp
+ );
+
if ( $val != $sessionToken ) {
wfDebug( "User::matchEditToken: broken session data\n" );
}
- return $val == $sessionToken;
+ return hash_equals( $sessionToken, $val );
}
/**
@@ -3946,11 +4141,12 @@ class User implements IDBAccessObject {
* @param string $val Input value to compare
* @param string $salt Optional function-specific data for hashing
* @param WebRequest|null $request Object to use or null to use $wgRequest
+ * @param int $maxage Fail tokens older than this, in seconds
* @return bool Whether the token matches
*/
- public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
- $sessionToken = $this->getEditToken( $salt, $request );
- return substr( $sessionToken, 0, 32 ) == substr( $val, 0, 32 );
+ public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
+ $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . self::EDIT_TOKEN_SUFFIX;
+ return $this->matchEditToken( $val, $salt, $request, $maxage );
}
/**
@@ -4085,7 +4281,7 @@ class User implements IDBAccessObject {
// and fire the ConfirmEmailComplete hook on redundant confirmations.
if ( !$this->isEmailConfirmed() ) {
$this->setEmailAuthenticationTimestamp( wfTimestampNow() );
- wfRunHooks( 'ConfirmEmailComplete', array( $this ) );
+ Hooks::run( 'ConfirmEmailComplete', array( $this ) );
}
return true;
}
@@ -4103,7 +4299,7 @@ class User implements IDBAccessObject {
$this->mEmailTokenExpires = null;
$this->setEmailAuthenticationTimestamp( null );
$this->mEmail = '';
- wfRunHooks( 'InvalidateEmailComplete', array( $this ) );
+ Hooks::run( 'InvalidateEmailComplete', array( $this ) );
return true;
}
@@ -4114,7 +4310,7 @@ class User implements IDBAccessObject {
public function setEmailAuthenticationTimestamp( $timestamp ) {
$this->load();
$this->mEmailAuthenticated = $timestamp;
- wfRunHooks( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
+ Hooks::run( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
}
/**
@@ -4128,7 +4324,7 @@ class User implements IDBAccessObject {
return false;
}
$canSend = $this->isEmailConfirmed();
- wfRunHooks( 'UserCanSendEmail', array( &$this, &$canSend ) );
+ Hooks::run( 'UserCanSendEmail', array( &$this, &$canSend ) );
return $canSend;
}
@@ -4155,7 +4351,7 @@ class User implements IDBAccessObject {
global $wgEmailAuthentication;
$this->load();
$confirmed = true;
- if ( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
+ if ( Hooks::run( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
if ( $this->isAnon() ) {
return false;
}
@@ -4313,7 +4509,7 @@ class User implements IDBAccessObject {
}
// Allow extensions (e.g. OAuth) to say false
- if ( !wfRunHooks( 'UserIsEveryoneAllowed', array( $right ) ) ) {
+ if ( !Hooks::run( 'UserIsEveryoneAllowed', array( $right ) ) ) {
$cache[$right] = false;
return false;
}
@@ -4361,7 +4557,7 @@ class User implements IDBAccessObject {
/**
* Get a list of all available permissions.
- * @return array Array of permission names
+ * @return string[] Array of permission names
*/
public static function getAllRights() {
if ( self::$mAllRights === false ) {
@@ -4371,7 +4567,7 @@ class User implements IDBAccessObject {
} else {
self::$mAllRights = self::$mCoreRights;
}
- wfRunHooks( 'UserGetAllRights', array( &self::$mAllRights ) );
+ Hooks::run( 'UserGetAllRights', array( &self::$mAllRights ) );
}
return self::$mAllRights;
}
@@ -4384,8 +4580,8 @@ class User implements IDBAccessObject {
global $wgImplicitGroups;
$groups = $wgImplicitGroups;
- # Deprecated, use $wgImplictGroups instead
- wfRunHooks( 'UserGetImplicitGroups', array( &$groups ) );
+ # Deprecated, use $wgImplicitGroups instead
+ Hooks::run( 'UserGetImplicitGroups', array( &$groups ), '1.25' );
return $groups;
}
@@ -4423,7 +4619,7 @@ class User implements IDBAccessObject {
if ( $title ) {
return Linker::link( $title, htmlspecialchars( $text ) );
} else {
- return $text;
+ return htmlspecialchars( $text );
}
}
@@ -4441,7 +4637,7 @@ class User implements IDBAccessObject {
}
$title = self::getGroupPage( $group );
if ( $title ) {
- $page = $title->getPrefixedText();
+ $page = $title->getFullText();
return "[[$page|$text]]";
} else {
return $text;
@@ -4478,6 +4674,7 @@ class User implements IDBAccessObject {
// Same thing for remove
if ( empty( $wgRemoveGroups[$group] ) ) {
+ // Do nothing
} elseif ( $wgRemoveGroups[$group] === true ) {
$groups['remove'] = self::getAllGroups();
} elseif ( is_array( $wgRemoveGroups[$group] ) ) {
@@ -4503,6 +4700,7 @@ class User implements IDBAccessObject {
// Now figure out what groups the user can add to him/herself
if ( empty( $wgGroupsAddToSelf[$group] ) ) {
+ // Do nothing
} elseif ( $wgGroupsAddToSelf[$group] === true ) {
// No idea WHY this would be used, but it's there
$groups['add-self'] = User::getAllGroups();
@@ -4511,6 +4709,7 @@ class User implements IDBAccessObject {
}
if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
+ // Do nothing
} elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
$groups['remove-self'] = User::getAllGroups();
} elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
@@ -4713,7 +4912,7 @@ class User implements IDBAccessObject {
if ( $action === true ) {
$action = 'byemail';
} elseif ( $action === false ) {
- if ( $this->getName() == $wgUser->getName() ) {
+ if ( $this->equals( $wgUser ) ) {
$action = 'create';
} else {
$action = 'create2';
@@ -4793,7 +4992,9 @@ class User implements IDBAccessObject {
if ( !is_array( $data ) ) {
wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
// Load from database
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
$res = $dbr->select(
'user_properties',
@@ -4816,7 +5017,7 @@ class User implements IDBAccessObject {
$this->mOptionsLoaded = true;
- wfRunHooks( 'UserLoadOptions', array( $this, &$this->mOptions ) );
+ Hooks::run( 'UserLoadOptions', array( $this, &$this->mOptions ) );
}
/**
@@ -4832,7 +5033,7 @@ class User implements IDBAccessObject {
// Allow hooks to abort, for instance to save to a global profile.
// Reset options to default state before saving.
- if ( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) {
+ if ( !Hooks::run( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) {
return;
}
@@ -4998,4 +5199,15 @@ class User implements IDBAccessObject {
return Status::newFatal( 'badaccess-group0' );
}
}
+
+ /**
+ * Checks if two user objects point to the same user.
+ *
+ * @since 1.25
+ * @param User $user
+ * @return bool
+ */
+ public function equals( User $user ) {
+ return $this->getName() === $user->getName();
+ }
}
diff --git a/includes/UserArray.php b/includes/UserArray.php
index 7da65827..31bd601c 100644
--- a/includes/UserArray.php
+++ b/includes/UserArray.php
@@ -27,7 +27,7 @@ abstract class UserArray implements Iterator {
*/
static function newFromResult( $res ) {
$userArray = null;
- if ( !wfRunHooks( 'UserArrayFromResult', array( &$userArray, $res ) ) ) {
+ if ( !Hooks::run( 'UserArrayFromResult', array( &$userArray, $res ) ) ) {
return null;
}
if ( $userArray === null ) {
@@ -57,6 +57,27 @@ abstract class UserArray implements Iterator {
}
/**
+ * @since 1.25
+ * @param array $names
+ * @return UserArrayFromResult
+ */
+ static function newFromNames( $names ) {
+ $names = array_map( 'strval', (array)$names ); // paranoia
+ if ( !$names ) {
+ // Database::select() doesn't like empty arrays
+ return new ArrayIterator( array() );
+ }
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ 'user',
+ User::selectFields(),
+ array( 'user_name' => array_unique( $names ) ),
+ __METHOD__
+ );
+ return self::newFromResult( $res );
+ }
+
+ /**
* @param ResultWrapper $res
* @return UserArrayFromResult
*/
diff --git a/includes/UserRightsProxy.php b/includes/UserRightsProxy.php
index 53c69d81..1b9e4b69 100644
--- a/includes/UserRightsProxy.php
+++ b/includes/UserRightsProxy.php
@@ -114,7 +114,8 @@ class 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,
+ // 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 );
@@ -211,6 +212,8 @@ class UserRightsProxy {
/**
* Replaces User::addUserGroup()
* @param string $group
+ *
+ * @return bool
*/
function addGroup( $group ) {
$this->db->insert( 'user_groups',
@@ -220,11 +223,15 @@ class UserRightsProxy {
),
__METHOD__,
array( 'IGNORE' ) );
+
+ return true;
}
/**
* Replaces User::removeUserGroup()
* @param string $group
+ *
+ * @return bool
*/
function removeGroup( $group ) {
$this->db->delete( 'user_groups',
@@ -233,6 +240,8 @@ class UserRightsProxy {
'ug_group' => $group,
),
__METHOD__ );
+
+ return true;
}
/**
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index ab136b89..4d226924 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -275,7 +275,6 @@ class WatchedItem {
* @return bool
*/
public static function batchAddWatch( array $items ) {
- $section = new ProfileSection( __METHOD__ );
if ( wfReadOnly() ) {
return false;
@@ -331,11 +330,9 @@ class WatchedItem {
* @return bool
*/
public function removeWatch() {
- wfProfileIn( __METHOD__ );
// Only loggedin user can have a watchlist
if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -370,7 +367,6 @@ class WatchedItem {
$this->watched = false;
- wfProfileOut( __METHOD__ );
return $success;
}
@@ -401,7 +397,8 @@ class WatchedItem {
$newtitle = $nt->getDBkey();
$dbw = wfGetDB( DB_MASTER );
- $res = $dbw->select( 'watchlist', 'wl_user',
+ $res = $dbw->select( 'watchlist',
+ array( 'wl_user', 'wl_notificationtimestamp' ),
array( 'wl_namespace' => $oldnamespace, 'wl_title' => $oldtitle ),
__METHOD__, 'FOR UPDATE'
);
@@ -411,7 +408,8 @@ class WatchedItem {
$values[] = array(
'wl_user' => $s->wl_user,
'wl_namespace' => $newnamespace,
- 'wl_title' => $newtitle
+ 'wl_title' => $newtitle,
+ 'wl_notificationtimestamp' => $s->wl_notificationtimestamp,
);
}
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index b187c4ac..054eceb9 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -51,15 +51,20 @@ class WebRequest {
private $ip;
/**
+ * The timestamp of the start of the request, with microsecond precision.
+ * @var float
+ */
+ protected $requestTime;
+
+ /**
* Cached URL protocol
* @var string
*/
protected $protocol;
public function __construct() {
- if ( function_exists( 'get_magic_quotes_gpc' ) && get_magic_quotes_gpc() ) {
- throw new MWException( "MediaWiki does not function when magic quotes are enabled." );
- }
+ $this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
+ ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
// POST overrides GET data
// We don't use $_REQUEST here to avoid interference from cookies...
@@ -138,7 +143,7 @@ class WebRequest {
);
}
- wfRunHooks( 'WebRequestPathInfoRouter', array( $router ) );
+ Hooks::run( 'WebRequestPathInfoRouter', array( $router ) );
$matches = $router->parse( $path );
}
@@ -207,9 +212,9 @@ class WebRequest {
* @return array
*/
public static function detectProtocol() {
- if ( ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ||
+ if ( ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ||
( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
- $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) ) {
+ $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) ) {
return 'https';
} else {
return 'http';
@@ -217,6 +222,17 @@ class WebRequest {
}
/**
+ * Get the number of seconds to have elapsed since request start,
+ * in fractional seconds, with microsecond resolution.
+ *
+ * @return float
+ * @since 1.25
+ */
+ public function getElapsedTime() {
+ return microtime( true ) - $this->requestTime;
+ }
+
+ /**
* Get the current URL protocol (http or https)
* @return string
*/
@@ -289,7 +305,7 @@ class WebRequest {
}
} else {
global $wgContLang;
- $data = isset( $wgContLang ) ? $wgContLang->normalize( $data ) : UtfNormal::cleanUp( $data );
+ $data = isset( $wgContLang ) ? $wgContLang->normalize( $data ) : UtfNormal\Validator::cleanUp( $data );
}
return $data;
}
@@ -705,21 +721,22 @@ class WebRequest {
/**
* Take an arbitrary query and rewrite the present URL to include it
+ * @deprecated Use appendQueryValue/appendQueryArray instead
* @param string $query Query string fragment; do not include initial '?'
- *
* @return string
*/
public function appendQuery( $query ) {
+ wfDeprecated( __METHOD__, '1.25' );
return $this->appendQueryArray( wfCgiToArray( $query ) );
}
/**
* @param string $key
* @param string $value
- * @param bool $onlyquery
+ * @param bool $onlyquery [deprecated]
* @return string
*/
- public function appendQueryValue( $key, $value, $onlyquery = false ) {
+ public function appendQueryValue( $key, $value, $onlyquery = true ) {
return $this->appendQueryArray( array( $key => $value ), $onlyquery );
}
@@ -727,16 +744,21 @@ class WebRequest {
* Appends or replaces value of query variables.
*
* @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
+ * @param bool $onlyquery Whether to only return the query string and not the complete URL [deprecated]
* @return string
*/
- public function appendQueryArray( $array, $onlyquery = false ) {
+ public function appendQueryArray( $array, $onlyquery = true ) {
global $wgTitle;
$newquery = $this->getQueryValues();
unset( $newquery['title'] );
$newquery = array_merge( $newquery, $array );
$query = wfArrayToCgi( $newquery );
- return $onlyquery ? $query : $wgTitle->getLocalURL( $query );
+ if ( !$onlyquery ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ return $wgTitle->getLocalURL( $query );
+ }
+
+ return $query;
}
/**
@@ -1112,7 +1134,7 @@ HTML;
}
# Allow extensions to improve our guess
- wfRunHooks( 'GetIP', array( &$ip ) );
+ Hooks::run( 'GetIP', array( &$ip ) );
if ( !$ip ) {
throw new MWException( "Unable to determine IP." );
@@ -1255,6 +1277,7 @@ class WebRequestUpload {
class FauxRequest extends WebRequest {
private $wasPosted = false;
private $session = array();
+ private $requestUrl;
/**
* @param array $data Array of *non*-urlencoded key => value pairs, the
@@ -1267,6 +1290,8 @@ class FauxRequest extends WebRequest {
public function __construct( $data = array(), $wasPosted = false,
$session = null, $protocol = 'http'
) {
+ $this->requestTime = microtime( true );
+
if ( is_array( $data ) ) {
$this->data = $data;
} else {
@@ -1334,8 +1359,15 @@ class FauxRequest extends WebRequest {
return false;
}
+ public function setRequestURL( $url ) {
+ $this->requestUrl = $url;
+ }
+
public function getRequestURL() {
- $this->notImplemented( __METHOD__ );
+ if ( $this->requestUrl === null ) {
+ throw new MWException( 'Request URL not set' );
+ }
+ return $this->requestUrl;
}
public function getProtocol() {
@@ -1483,4 +1515,8 @@ class DerivativeRequest extends FauxRequest {
public function getProtocol() {
return $this->base->getProtocol();
}
+
+ public function getElapsedTime() {
+ return $this->base->getElapsedTime();
+ }
}
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index ad9f4e66..ab34931c 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -38,9 +38,25 @@ class WebResponse {
}
/**
+ * Get a response header
+ * @param string $key The name of the header to get (case insensitive).
+ * @return string|null The header value (if set); null otherwise.
+ * @since 1.25
+ */
+ public function getHeader( $key ) {
+ foreach ( headers_list() as $header ) {
+ list( $name, $val ) = explode( ':', $header, 2 );
+ if ( !strcasecmp( $name, $key ) ) {
+ return trim( $val );
+ }
+ }
+ return null;
+ }
+
+ /**
* Set the browser cookie
- * @param string $name Name of cookie
- * @param string $value Value to give cookie
+ * @param string $name The name of the cookie.
+ * @param string $value The value to be stored in the 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.
@@ -56,7 +72,7 @@ class WebResponse {
* 'prefix', 'domain', and 'secure'
* @since 1.22 Replaced $prefix, $domain, and $forceSecure with $options
*/
- public function setcookie( $name, $value, $expire = 0, $options = null ) {
+ public function setcookie( $name, $value, $expire = 0, $options = array() ) {
global $wgCookiePath, $wgCookiePrefix, $wgCookieDomain;
global $wgCookieSecure, $wgCookieExpiration, $wgCookieHttpOnly;
@@ -89,7 +105,7 @@ class WebResponse {
$func = $options['raw'] ? 'setrawcookie' : 'setcookie';
- if ( wfRunHooks( 'WebResponseSetCookie', array( &$name, &$value, &$expire, $options ) ) ) {
+ if ( Hooks::run( 'WebResponseSetCookie', array( &$name, &$value, &$expire, $options ) ) ) {
wfDebugLog( 'cookie',
$func . ': "' . implode( '", "',
array(
@@ -148,9 +164,9 @@ class FauxResponse extends WebResponse {
/**
* @param string $key The name of the header to get (case insensitive).
- * @return string
+ * @return string|null The header value (if set); null otherwise.
*/
- public function getheader( $key ) {
+ public function getHeader( $key ) {
$key = strtoupper( $key );
if ( isset( $this->headers[$key] ) ) {
@@ -169,20 +185,18 @@ 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 The name of the cookie.
+ * @param string $value The value to be stored in the cookie.
+ * @param int|null $expire Ignored in this faux subclass.
+ * @param array $options Ignored in this faux subclass.
*/
- public function setcookie( $name, $value, $expire = 0, $options = null ) {
+ public function setcookie( $name, $value, $expire = 0, $options = array() ) {
$this->cookies[$name] = $value;
}
/**
* @param string $name
- * @return string
+ * @return string|null
*/
public function getcookie( $name ) {
if ( isset( $this->cookies[$name] ) ) {
diff --git a/includes/WebStart.php b/includes/WebStart.php
index 2ae72dcc..9c71f3e1 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -6,7 +6,7 @@
* MW_NO_SETUP is defined.
*
* Setup.php (if loaded) then sets up GlobalFunctions, the AutoLoader,
- * and the configuration globals (though not $wgTitle).
+ * and the configuration globals.
*
* This 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,12 +34,30 @@ if ( ini_get( 'register_globals' ) ) {
. 'for help on how to disable it.' );
}
+if ( function_exists( 'get_magic_quotes_gpc' ) && get_magic_quotes_gpc() ) {
+ die( 'MediaWiki does not function when magic quotes are enabled. '
+ . 'Please see the <a href="https://php.net/manual/security.magicquotes.disabling.php">PHP Manual</a> '
+ . 'for help on how to disable magic quotes.' );
+}
+
+
# bug 15461: Make IE8 turn off content sniffing. Everybody else should ignore this
# We're adding it here so that it's *always* set, even for alternate entry
# points and when $wgOut gets disabled or overridden.
header( 'X-Content-Type-Options: nosniff' );
-$wgRequestTime = microtime( true );
+# Approximate $_SERVER['REQUEST_TIME_FLOAT'] for PHP<5.4
+if ( !isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ) {
+ $_SERVER['REQUEST_TIME_FLOAT'] = microtime( true );
+}
+
+/**
+ * @var float Request start time as fractional seconds since epoch
+ * @deprecated since 1.25; use $_SERVER['REQUEST_TIME_FLOAT'] or
+ * WebRequest::getElapsedTime() instead.
+ */
+$wgRequestTime = $_SERVER['REQUEST_TIME_FLOAT'];
+
unset( $IP );
# Valid web server entry point, enable includes.
@@ -58,8 +76,8 @@ if ( $IP === false ) {
$IP = realpath( '.' ) ?: dirname( __DIR__ );
}
-# Load the profiler
-require_once "$IP/includes/profiler/Profiler.php";
+# Grab profiling functions
+require_once "$IP/includes/profiler/ProfilerFunctions.php";
$wgRUstart = wfGetRusage() ?: array();
# Start the autoloader, so that extensions can derive classes from core files
@@ -74,11 +92,13 @@ if ( file_exists( "$IP/StartProfiler.php" ) ) {
require "$IP/StartProfiler.php";
}
-wfProfileIn( 'WebStart.php-conf' );
# Load default settings
require_once "$IP/includes/DefaultSettings.php";
+# Load global functions
+require_once "$IP/includes/GlobalFunctions.php";
+
# Load composer's autoloader if present
if ( is_readable( "$IP/vendor/autoload.php" ) ) {
require_once "$IP/vendor/autoload.php";
@@ -96,7 +116,7 @@ if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
# the wiki installer needs to be launched or the generated file uploaded to
# 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";
+ require_once "$IP/includes/NoLocalSettings.php";
die();
}
@@ -104,18 +124,15 @@ if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
require_once MW_CONFIG_FILE;
}
-wfProfileOut( 'WebStart.php-conf' );
-wfProfileIn( 'WebStart.php-ob_start' );
# Initialise output buffering
# Check that there is no previous output or previously set up buffers, because
# that would cause us to potentially mix gzip and non-gzip output, creating a
# big mess.
-if ( !defined( 'MW_NO_OUTPUT_BUFFER' ) && ob_get_level() == 0 ) {
+if ( ob_get_level() == 0 ) {
require_once "$IP/includes/OutputHandler.php";
ob_start( 'wfOutputHandler' );
}
-wfProfileOut( 'WebStart.php-ob_start' );
if ( !defined( 'MW_NO_SETUP' ) ) {
require_once "$IP/includes/Setup.php";
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index 34cd48da..f16f5aa7 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -161,7 +161,7 @@ class WikiReference {
}
/**
- * Get the the URL in a way to de displayed to the user
+ * Get the URL in a way to be displayed to the user
* More or less Wikimedia specific
*
* @return string
diff --git a/includes/Xml.php b/includes/Xml.php
index c6c02867..f0bd70b2 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -94,9 +94,7 @@ class Xml {
$attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
}
if ( $contents ) {
- wfProfileIn( __METHOD__ . '-norm' );
$contents = $wgContLang->normalize( $contents );
- wfProfileOut( __METHOD__ . '-norm' );
}
return self::element( $element, $attribs, $contents );
}
@@ -368,12 +366,10 @@ class Xml {
public static function label( $label, $id, $attribs = array() ) {
$a = array( 'for' => $id );
- # FIXME avoid copy pasting below:
- if ( isset( $attribs['class'] ) ) {
- $a['class'] = $attribs['class'];
- }
- if ( isset( $attribs['title'] ) ) {
- $a['title'] = $attribs['title'];
+ foreach ( array( 'class', 'title' ) as $attr ) {
+ if ( isset( $attribs[$attr] ) ) {
+ $a[$attr] = $attribs[$attr];
+ }
}
return self::element( 'label', $a, $label );
diff --git a/includes/ZhConversion.php b/includes/ZhConversion.php
index 651b1140..4be27513 100644
--- a/includes/ZhConversion.php
+++ b/includes/ZhConversion.php
@@ -9,6 +9,8 @@
*/
$zh2Hant = array(
+'㐷' => '傌',
+'㐹' => '㑶',
'㐽' => '偑',
'㑇' => '㑳',
'㑈' => '倲',
@@ -72,10 +74,13 @@ $zh2Hant = array(
'䌼' => '綐',
'䌽' => '綵',
'䌾' => '䋻',
+'䌿' => '䋹',
'䍀' => '繿',
'䍁' => '繸',
-'䑽' => '𦪙',
+'䎬' => '䎱',
+'䏝' => '膞',
'䓕' => '薳',
+'䓖' => '藭',
'䗖' => '螮',
'䘛' => '𧝞',
'䘞' => '𧜗',
@@ -142,6 +147,7 @@ $zh2Hant = array(
'为' => '為',
'丽' => '麗',
'举' => '舉',
+'么' => '麼',
'义' => '義',
'乌' => '烏',
'乐' => '樂',
@@ -185,6 +191,7 @@ $zh2Hant = array(
'伪' => '偽',
'伫' => '佇',
'体' => '體',
+'佣' => '傭',
'佥' => '僉',
'侠' => '俠',
'侣' => '侶',
@@ -235,6 +242,7 @@ $zh2Hant = array(
'况' => '況',
'冻' => '凍',
'净' => '凈',
+'凄' => '淒',
'凉' => '涼',
'减' => '減',
'凑' => '湊',
@@ -290,7 +298,6 @@ $zh2Hant = array(
'卤' => '鹵',
'卫' => '衛',
'却' => '卻',
-'卺' => '巹',
'厂' => '廠',
'厅' => '廳',
'历' => '歷',
@@ -309,6 +316,8 @@ $zh2Hant = array(
'县' => '縣',
'叁' => '叄',
'参' => '參',
+'叆' => '靉',
+'叇' => '靆',
'双' => '雙',
'发' => '發',
'变' => '變',
@@ -341,7 +350,6 @@ $zh2Hant = array(
'咙' => '嚨',
'咛' => '嚀',
'咝' => '噝',
-'咤' => '吒',
'响' => '響',
'哑' => '啞',
'哒' => '噠',
@@ -362,7 +370,7 @@ $zh2Hant = array(
'啧' => '嘖',
'啬' => '嗇',
'啭' => '囀',
-'啮' => '嚙',
+'啮' => '齧',
'啯' => '嘓',
'啰' => '囉',
'啴' => '嘽',
@@ -425,7 +433,6 @@ $zh2Hant = array(
'复' => '復',
'够' => '夠',
'头' => '頭',
-'夸' => '誇',
'夹' => '夾',
'夺' => '奪',
'奁' => '奩',
@@ -503,6 +510,7 @@ $zh2Hant = array(
'岭' => '嶺',
'岽' => '崬',
'岿' => '巋',
+'峃' => '嶨',
'峄' => '嶧',
'峡' => '峽',
'峣' => '嶢',
@@ -533,9 +541,10 @@ $zh2Hant = array(
'帻' => '幘',
'帼' => '幗',
'幂' => '冪',
-'幞' => '襆',
'并' => '並',
+'幺' => '么',
'广' => '廣',
+'庄' => '莊',
'庆' => '慶',
'庐' => '廬',
'庑' => '廡',
@@ -544,6 +553,7 @@ $zh2Hant = array(
'庙' => '廟',
'庞' => '龐',
'废' => '廢',
+'庼' => '廎',
'廪' => '廩',
'开' => '開',
'异' => '異',
@@ -723,7 +733,7 @@ $zh2Hant = array(
'杀' => '殺',
'杂' => '雜',
'权' => '權',
-'杆' => '桿',
+'杠' => '槓',
'条' => '條',
'来' => '來',
'杨' => '楊',
@@ -783,6 +793,7 @@ $zh2Hant = array(
'椤' => '欏',
'椫' => '樿',
'椭' => '橢',
+'椮' => '槮',
'楼' => '樓',
'榄' => '欖',
'榅' => '榲',
@@ -856,6 +867,7 @@ $zh2Hant = array(
'浆' => '漿',
'浇' => '澆',
'浈' => '湞',
+'浉' => '溮',
'浊' => '濁',
'测' => '測',
'浍' => '澮',
@@ -888,6 +900,7 @@ $zh2Hant = array(
'渐' => '漸',
'渑' => '澠',
'渔' => '漁',
+'渖' => '瀋',
'渗' => '滲',
'温' => '溫',
'湾' => '灣',
@@ -899,7 +912,7 @@ $zh2Hant = array(
'滗' => '潷',
'滚' => '滾',
'滞' => '滯',
-'滟' => '灧',
+'滟' => '灩',
'滠' => '灄',
'满' => '滿',
'滢' => '瀅',
@@ -928,6 +941,7 @@ $zh2Hant = array(
'灿' => '燦',
'炀' => '煬',
'炉' => '爐',
+'炖' => '燉',
'炜' => '煒',
'炝' => '熗',
'点' => '點',
@@ -980,6 +994,7 @@ $zh2Hant = array(
'献' => '獻',
'獭' => '獺',
'玑' => '璣',
+'玙' => '璵',
'玚' => '瑒',
'玛' => '瑪',
'玮' => '瑋',
@@ -991,11 +1006,13 @@ $zh2Hant = array(
'珑' => '瓏',
'珰' => '璫',
'珲' => '琿',
+'琎' => '璡',
'琏' => '璉',
'琐' => '瑣',
'琼' => '瓊',
'瑶' => '瑤',
'瑷' => '璦',
+'瑸' => '璸',
'璎' => '瓔',
'瓒' => '瓚',
'瓯' => '甌',
@@ -1013,7 +1030,6 @@ $zh2Hant = array(
'疮' => '瘡',
'疯' => '瘋',
'疱' => '皰',
-'疴' => '痾',
'痈' => '癰',
'痉' => '痙',
'痒' => '癢',
@@ -1021,6 +1037,7 @@ $zh2Hant = array(
'痨' => '癆',
'痪' => '瘓',
'痫' => '癇',
+'痳' => '痲',
'瘅' => '癉',
'瘆' => '瘮',
'瘗' => '瘞',
@@ -1072,10 +1089,11 @@ $zh2Hant = array(
'硚' => '礄',
'确' => '確',
'硵' => '磠',
-'硷' => '礆',
+'硷' => '鹼',
'碍' => '礙',
'碛' => '磧',
'碜' => '磣',
+'碱' => '鹼',
'礼' => '禮',
'祃' => '禡',
'祎' => '禕',
@@ -1221,7 +1239,7 @@ $zh2Hant = array(
'绠' => '綆',
'绡' => '綃',
'绢' => '絹',
-'绣' => '綉',
+'绣' => '繡',
'绤' => '綌',
'绥' => '綏',
'绦' => '絛',
@@ -1235,13 +1253,13 @@ $zh2Hant = array(
'绮' => '綺',
'绯' => '緋',
'绰' => '綽',
-'绱' => '緔',
+'绱' => '鞝',
'绲' => '緄',
'绳' => '繩',
'维' => '維',
'绵' => '綿',
'绶' => '綬',
-'绷' => '綳',
+'绷' => '繃',
'绸' => '綢',
'绹' => '綯',
'绺' => '綹',
@@ -1266,6 +1284,7 @@ $zh2Hant = array(
'缍' => '綞',
'缎' => '緞',
'缏' => '緶',
+'缐' => '線',
'缑' => '緱',
'缒' => '縋',
'缓' => '緩',
@@ -1344,7 +1363,7 @@ $zh2Hant = array(
'胶' => '膠',
'脉' => '脈',
'脍' => '膾',
-'脏' => '臟',
+'脏' => '髒',
'脐' => '臍',
'脑' => '腦',
'脓' => '膿',
@@ -1384,7 +1403,6 @@ $zh2Hant = array(
'苎' => '苧',
'苏' => '蘇',
'苧' => '薴',
-'苹' => '蘋',
'茎' => '莖',
'茏' => '蘢',
'茑' => '蔦',
@@ -1414,7 +1432,7 @@ $zh2Hant = array(
'荬' => '蕒',
'荭' => '葒',
'荮' => '葤',
-'药' => '葯',
+'药' => '藥',
'莅' => '蒞',
'莱' => '萊',
'莲' => '蓮',
@@ -1425,7 +1443,8 @@ $zh2Hant = array(
'莸' => '蕕',
'莹' => '瑩',
'莺' => '鶯',
-'莼' => '蒓',
+'莼' => '蓴',
+'萚' => '蘀',
'萝' => '蘿',
'萤' => '螢',
'营' => '營',
@@ -1667,7 +1686,7 @@ $zh2Hant = array(
'谢' => '謝',
'谣' => '謠',
'谤' => '謗',
-'谥' => '謚',
+'谥' => '諡',
'谦' => '謙',
'谧' => '謐',
'谨' => '謹',
@@ -1890,7 +1909,6 @@ $zh2Hant = array(
'鉴' => '鑒',
'銮' => '鑾',
'錾' => '鏨',
-'鎭' => '鎮',
'钅' => '釒',
'钆' => '釓',
'钇' => '釔',
@@ -1927,7 +1945,7 @@ $zh2Hant = array(
'钦' => '欽',
'钧' => '鈞',
'钨' => '鎢',
-'钩' => '鉤',
+'钩' => '鈎',
'钪' => '鈧',
'钫' => '鈁',
'钬' => '鈥',
@@ -2089,7 +2107,7 @@ $zh2Hant = array(
'镈' => '鎛',
'镉' => '鎘',
'镊' => '鑷',
-'镋' => '鎲',
+'镋' => '钂',
'镌' => '鐫',
'镍' => '鎳',
'镎' => '鎿',
@@ -2144,7 +2162,7 @@ $zh2Hant = array(
'闯' => '闖',
'闰' => '閏',
'闱' => '闈',
-'闲' => '閑',
+'闲' => '閒',
'闳' => '閎',
'间' => '間',
'闵' => '閔',
@@ -2218,7 +2236,6 @@ $zh2Hant = array(
'鞑' => '韃',
'鞒' => '鞽',
'鞯' => '韉',
-'鞲' => '韝',
'韦' => '韋',
'韧' => '韌',
'韨' => '韍',
@@ -2515,6 +2532,7 @@ $zh2Hant = array(
'鳡' => '鱤',
'鳢' => '鱧',
'鳣' => '鱣',
+'鳤' => '䲘',
'鸟' => '鳥',
'鸠' => '鳩',
'鸡' => '雞',
@@ -2604,6 +2622,7 @@ $zh2Hant = array(
'鹾' => '鹺',
'麦' => '麥',
'麸' => '麩',
+'麹' => '麴',
'黄' => '黃',
'黉' => '黌',
'黡' => '黶',
@@ -2614,7 +2633,6 @@ $zh2Hant = array(
'鼍' => '鼉',
'鼗' => '鞀',
'鼹' => '鼴',
-'齄' => '齇',
'齐' => '齊',
'齑' => '齏',
'齿' => '齒',
@@ -2642,7 +2660,6 @@ $zh2Hant = array(
'𠚳' => '𠠎',
'𠛅' => '剾',
'𠛆' => '𠞆',
-'𠮶' => '嗰',
'𠯟' => '哯',
'𠯠' => '噅',
'𠲥' => '𡅏',
@@ -2872,6 +2889,7 @@ $zh2Hant = array(
'𩧿' => '䮠',
'𩨀' => '騔',
'𩨁' => '䮞',
+'𩨂' => '驄',
'𩨃' => '騝',
'𩨄' => '騪',
'𩨅' => '𩤸',
@@ -2897,7 +2915,6 @@ $zh2Hant = array(
'𩽽' => '𩶱',
'𩽾' => '鮟',
'𩽿' => '𩶰',
-'𩾀' => '鮕',
'𩾁' => '鯄',
'𩾂' => '䲖',
'𩾃' => '鮸',
@@ -2926,6 +2943,7 @@ $zh2Hant = array(
'𪉐' => '𪃍',
'𪉑' => '鷔',
'𪉒' => '𪄕',
+'𪉓' => '𪈼',
'𪉔' => '𪄆',
'𪉕' => '𪇳',
'𪎈' => '䴬',
@@ -2951,7 +2969,6 @@ $zh2Hant = array(
'𫌀' => '襀',
'𫌨' => '覼',
'𫍙' => '訑',
-'𫍟' => '詑',
'𫍢' => '譊',
'𫍰' => '諰',
'𫍲' => '謏',
@@ -2985,14 +3002,18 @@ $zh2Hant = array(
'𫛢' => '鸋',
'𫛶' => '鶒',
'𫛸' => '鶗',
+'0出現' => '0出現',
+'0出现' => '0出現',
+'0出線' => '0出線',
+'0出线' => '0出線',
'0只支持' => '0只支持',
'0只支援' => '0只支援',
'0周后' => '0周後',
-'0多只' => '0多隻',
'0天后' => '0天後',
'0年' => '0年',
'0只' => '0隻',
'0余' => '0餘',
+'0出' => '0齣',
'1只支持' => '1只支持',
'1只支援' => '1只支援',
'1周后' => '1周後',
@@ -3057,12 +3078,15 @@ $zh2Hant = array(
'9只' => '9隻',
'9余' => '9餘',
'·范' => '·范',
-'、克制' => '、剋制',
-'。克制' => '。剋制',
+'’s' => '’s',
'〇周后' => '〇周後',
'〇年' => '〇年',
'〇只' => '〇隻',
'〇余' => '〇餘',
+'“' => '「',
+'”' => '」',
+'‘' => '『',
+'’' => '』',
'一干二净' => '一乾二淨',
'一伙人' => '一伙人',
'一伙头' => '一伙頭',
@@ -3070,15 +3094,8 @@ $zh2Hant = array(
'一并' => '一併',
'一个' => '一個',
'一个准' => '一個準',
-'一出刊' => '一出刊',
-'一出口' => '一出口',
-'一出版' => '一出版',
-'一出生' => '一出生',
-'一出祁山' => '一出祁山',
-'一出逃' => '一出逃',
'一划' => '一划',
'一半只' => '一半只',
-'一吊錢' => '一吊錢',
'一吊钱' => '一吊錢',
'一周后' => '一周後',
'一地里' => '一地裡',
@@ -3092,7 +3109,6 @@ $zh2Hant = array(
'一干部下' => '一干部下',
'一年' => '一年',
'一年里' => '一年裡',
-'一吊' => '一弔',
'一别头' => '一彆頭',
'一斗斗' => '一斗斗',
'一树百获' => '一樹百穫',
@@ -3102,23 +3118,23 @@ $zh2Hant = array(
'一目了然' => '一目了然',
'一扎' => '一紮',
'一冲' => '一衝',
+'一厘一毫' => '一釐一毫',
'一锅面' => '一鍋麵',
'一只' => '一隻',
'一面食' => '一面食',
-'一头长发' => '一頭長髮',
'一余' => '一餘',
'一发千钧' => '一髮千鈞',
'一哄而散' => '一鬨而散',
+'一出剧' => '一齣劇',
+'一出喜剧' => '一齣喜劇',
+'一出好戏' => '一齣好戲',
'一出子' => '一齣子',
+'一出悲剧' => '一齣悲劇',
+'一出戏' => '一齣戲',
+'一出电影' => '一齣電影',
'丁丁当当' => '丁丁當當',
'丁丑' => '丁丑',
'七个' => '七個',
-'七出刊' => '七出刊',
-'七出口' => '七出口',
-'七出版' => '七出版',
-'七出生' => '七出生',
-'七出祁山' => '七出祁山',
-'七出逃' => '七出逃',
'七周后' => '七周後',
'七天后' => '七天後',
'七年' => '七年',
@@ -3129,12 +3145,6 @@ $zh2Hant = array(
'万俟' => '万俟',
'万旗' => '万旗',
'三个' => '三個',
-'三出刊' => '三出刊',
-'三出口' => '三出口',
-'三出版' => '三出版',
-'三出生' => '三出生',
-'三出祁山' => '三出祁山',
-'三出逃' => '三出逃',
'三周后' => '三周後',
'三天后' => '三天後',
'三年' => '三年',
@@ -3143,52 +3153,52 @@ $zh2Hant = array(
'三扎' => '三紮',
'三统历' => '三統曆',
'三统历史' => '三統歷史',
-'三复' => '三複',
'三只' => '三隻',
'三余' => '三餘',
+'三出戏' => '三齣戲',
+'上天里' => '上天里',
'上梁山' => '上梁山',
'上梁' => '上樑',
+'上台面' => '上檯面',
'上签名' => '上簽名',
'上签字' => '上簽字',
+'上签定' => '上簽定',
'上签写' => '上簽寫',
'上签收' => '上簽收',
+'上签发' => '上簽發',
+'上签约' => '上簽約',
+'上签署' => '上簽署',
+'上签订' => '上簽訂',
'上签' => '上籤',
-'上药' => '上藥',
+'上系上' => '上繫上',
'上课钟' => '上課鐘',
'上面糊' => '上面糊',
-'下仑路' => '下崙路',
'下于' => '下於',
'下梁' => '下樑',
'下注解' => '下注解',
'下签名' => '下簽名',
'下签字' => '下簽字',
+'下签定' => '下簽定',
'下签写' => '下簽寫',
'下签收' => '下簽收',
+'下签发' => '下簽發',
+'下签约' => '下簽約',
+'下签署' => '下簽署',
+'下签订' => '下簽訂',
'下签' => '下籤',
-'下药' => '下藥',
'下课钟' => '下課鐘',
'不干不净' => '不乾不淨',
-'不占' => '不佔',
'不克自制' => '不克自制',
-'不准他' => '不准他',
-'不准你' => '不准你',
-'不准她' => '不准她',
-'不准它' => '不准它',
-'不准我' => '不准我',
-'不准没' => '不准沒',
-'不准翻印' => '不准翻印',
-'不准许' => '不准許',
-'不准谁' => '不准誰',
-'不克制' => '不剋制',
'不加自制' => '不加自制',
'不占凶吉' => '不占凶吉',
'不占卜' => '不占卜',
'不占吉凶' => '不占吉凶',
'不占算' => '不占算',
'不只' => '不只',
+'不太准' => '不太準',
'不好干涉' => '不好干涉',
-'不好干预' => '不好干預',
'不好干預' => '不好干預',
+'不好干预' => '不好干預',
'不嫌母丑' => '不嫌母醜',
'不寒而栗' => '不寒而慄',
'不干事' => '不干事',
@@ -3203,30 +3213,27 @@ $zh2Hant = array(
'不干涉' => '不干涉',
'不干牠' => '不干牠',
'不干犯' => '不干犯',
-'不干预' => '不干預',
'不干預' => '不干預',
+'不干预' => '不干預',
'不干' => '不幹',
'不吊' => '不弔',
+'不卷' => '不捲',
'不采' => '不採',
'不斗膽' => '不斗膽',
'不斗胆' => '不斗膽',
'不断发' => '不斷發',
'不每只' => '不每只',
-'不准' => '不準',
-'不准确' => '不準確',
'不谷' => '不穀',
-'不药而愈' => '不藥而癒',
'不托' => '不託',
'不负所托' => '不負所托',
'不通吊庆' => '不通弔慶',
'不丑' => '不醜',
-'不采声' => '不采聲',
'不采聲' => '不采聲',
+'不采声' => '不采聲',
'不锈钢' => '不鏽鋼',
'不食干腊' => '不食乾腊',
'不斗' => '不鬥',
'丑三' => '丑三',
-'丑婆子' => '丑婆子',
'丑年' => '丑年',
'丑日' => '丑日',
'丑旦' => '丑旦',
@@ -3237,13 +3244,10 @@ $zh2Hant = array(
'且于' => '且於',
'世田谷' => '世田谷',
'世界杯' => '世界盃',
-'世界里' => '世界裡',
+'世纪里' => '世紀裡',
'世纪钟' => '世紀鐘',
'世纪钟表' => '世紀鐘錶',
'丢丑' => '丟醜',
-'并不准' => '並不准',
-'并不准确' => '並不準確',
-'并存着' => '並存著',
'并曰入淀' => '並曰入澱',
'并发动' => '並發動',
'并发展' => '並發展',
@@ -3256,20 +3260,24 @@ $zh2Hant = array(
'中型钟表面' => '中型鐘表面',
'中型钟表' => '中型鐘錶',
'中型钟面' => '中型鐘面',
-'中仑' => '中崙',
'中岳' => '中嶽',
'中庄子' => '中庄子',
'中文里' => '中文裡',
'中于' => '中於',
+'中签名' => '中簽名',
+'中签字' => '中簽字',
+'中签定' => '中簽定',
+'中签写' => '中簽寫',
+'中签收' => '中簽收',
+'中签发' => '中簽發',
+'中签约' => '中簽約',
+'中签署' => '中簽署',
+'中签订' => '中簽訂',
'中签' => '中籤',
-'中美发表' => '中美發表',
-'中药' => '中藥',
-'中西合并' => '中西合併',
'中风后' => '中風後',
'丰儀' => '丰儀',
'丰仪' => '丰儀',
'丰南' => '丰南',
-'丰台' => '丰台',
'丰姿' => '丰姿',
'丰容' => '丰容',
'丰度' => '丰度',
@@ -3280,14 +3288,20 @@ $zh2Hant = array(
'丰神' => '丰神',
'丰茸' => '丰茸',
'丰采' => '丰采',
-'丰韵' => '丰韻',
'丰韻' => '丰韻',
-'丸药' => '丸藥',
-'丹药' => '丹藥',
+'丰韵' => '丰韻',
'主仆' => '主僕',
'主干' => '主幹',
'主钟差' => '主鐘差',
'主钟曲线' => '主鐘曲線',
+'乃系' => '乃係',
+'么么唱唱' => '么么唱唱',
+'么儿' => '么兒',
+'么喝' => '么喝',
+'么妹' => '么妹',
+'么弟' => '么弟',
+'么爷' => '么爺',
+'么雞' => '么雞',
'么么小丑' => '么麼小丑',
'之一只' => '之一只',
'之二只' => '之二只',
@@ -3295,16 +3309,11 @@ $zh2Hant = array(
'之征' => '之徵',
'之托' => '之託',
'之钟' => '之鐘',
+'之鉴' => '之鑑',
'之余' => '之餘',
'乙丑' => '乙丑',
'九世之仇' => '九世之讎',
'九个' => '九個',
-'九出刊' => '九出刊',
-'九出口' => '九出口',
-'九出版' => '九出版',
-'九出生' => '九出生',
-'九出祁山' => '九出祁山',
-'九出逃' => '九出逃',
'九周后' => '九周後',
'九天后' => '九天後',
'九年' => '九年',
@@ -3312,13 +3321,12 @@ $zh2Hant = array(
'九扎' => '九紮',
'九只' => '九隻',
'九余' => '九餘',
-'九龙表行' => '九龍表行',
-'九龍表行' => '九龍表行',
-'也克制' => '也剋制',
'也斗了胆' => '也斗了膽',
+'干上' => '乾上',
'干干' => '乾乾',
'干干儿的' => '乾乾兒的',
'干干净净' => '乾乾淨淨',
+'干了' => '乾了',
'干井' => '乾井',
'干个够' => '乾個夠',
'干儿' => '乾兒',
@@ -3327,7 +3335,6 @@ $zh2Hant = array(
'干刻版' => '乾刻版',
'干剥剥' => '乾剝剝',
'干卦' => '乾卦',
-'干吊着下巴' => '乾吊著下巴',
'干和' => '乾和',
'干咳' => '乾咳',
'干咽' => '乾咽',
@@ -3357,6 +3364,7 @@ $zh2Hant = array(
'干巴' => '乾巴',
'干式' => '乾式',
'干弟' => '乾弟',
+'干得' => '乾得',
'干急' => '乾急',
'干性' => '乾性',
'干打雷' => '乾打雷',
@@ -3393,13 +3401,13 @@ $zh2Hant = array(
'干湿' => '乾濕',
'干熬' => '乾熬',
'干热' => '乾熱',
-'干熱' => '乾熱',
'干灯盏' => '乾燈盞',
'干燥' => '乾燥',
'干爸' => '乾爸',
'干爹' => '乾爹',
'干爽' => '乾爽',
'干片' => '乾片',
+'干物' => '乾物',
'干生受' => '乾生受',
'干生子' => '乾生子',
'干产' => '乾產',
@@ -3437,7 +3445,6 @@ $zh2Hant = array(
'干草' => '乾草',
'干菜' => '乾菜',
'干落' => '乾落',
-'干着' => '乾著',
'干姜' => '乾薑',
'干薪' => '乾薪',
'干虔' => '乾虔',
@@ -3471,27 +3478,21 @@ $zh2Hant = array(
'干鱼' => '乾魚',
'干鲜' => '乾鮮',
'干面' => '乾麵',
+'乱发生' => '亂發生',
+'乱发脾气' => '亂發脾氣',
'乱发' => '亂髮',
'乱哄' => '亂鬨',
'乱哄不过来' => '亂鬨不過來',
-'了克制' => '了剋制',
'了然后' => '了然後',
'事情干脆' => '事情干脆',
'事有斗巧' => '事有鬥巧',
-'事迹' => '事迹',
+'事里' => '事裡',
'事都干脆' => '事都干脆',
'二不棱登' => '二不稜登',
'二个' => '二個',
-'二出刊' => '二出刊',
-'二出口' => '二出口',
-'二出版' => '二出版',
-'二出生' => '二出生',
-'二出祁山' => '二出祁山',
-'二出逃' => '二出逃',
'二只得' => '二只得',
'二周后' => '二周後',
'二天后' => '二天後',
-'二仑' => '二崙',
'二年' => '二年',
'二缶钟惑' => '二缶鐘惑',
'二老板' => '二老板',
@@ -3506,13 +3507,13 @@ $zh2Hant = array(
'于仲文' => '于仲文',
'于佳卉' => '于佳卉',
'于来山' => '于來山',
-'于伟国' => '于偉國',
'于偉國' => '于偉國',
+'于伟国' => '于偉國',
'于光新' => '于光新',
'于光遠' => '于光遠',
'于光远' => '于光遠',
-'于克-蘭多縣' => '于克-蘭多縣',
'于克-兰多县' => '于克-蘭多縣',
+'于克-蘭多縣' => '于克-蘭多縣',
'于克勒' => '于克勒',
'于再清' => '于再清',
'于冕' => '于冕',
@@ -3523,20 +3524,20 @@ $zh2Hant = array(
'于化虎' => '于化虎',
'于占元' => '于占元',
'于友泽' => '于友澤',
-'于台煙' => '于台煙',
'于台烟' => '于台煙',
+'于台煙' => '于台煙',
'于右任' => '于右任',
'于吉' => '于吉',
'于和伟' => '于和偉',
'于品海' => '于品海',
-'于国桢' => '于國楨',
'于國楨' => '于國楨',
+'于国桢' => '于國楨',
'于国治' => '于國治',
'于國治' => '于國治',
-'于坚' => '于堅',
'于堅' => '于堅',
-'于大寶' => '于大寶',
+'于坚' => '于堅',
'于大宝' => '于大寶',
+'于大寶' => '于大寶',
'于天仁' => '于天仁',
'于天龙' => '于天龍',
'于奇库杜克' => '于奇庫杜克',
@@ -3546,31 +3547,32 @@ $zh2Hant = array(
'于娟' => '于娟',
'于子千' => '于子千',
'于孔兼' => '于孔兼',
-'于學忠' => '于學忠',
'于学忠' => '于學忠',
+'于學忠' => '于學忠',
'于家堡' => '于家堡',
'于寘' => '于寘',
+'于宝轩' => '于寶軒',
'于小伟' => '于小偉',
'于小偉' => '于小偉',
'于小彤' => '于小彤',
'于小惠' => '于小惠',
'于少保' => '于少保',
'于山' => '于山',
-'于山国' => '于山國',
'于山國' => '于山國',
-'于帥' => '于帥',
+'于山国' => '于山國',
'于帅' => '于帥',
+'于帥' => '于帥',
'于幼軍' => '于幼軍',
'于幼军' => '于幼軍',
'于康震' => '于康震',
-'于廣洲' => '于廣洲',
'于广洲' => '于廣洲',
+'于廣洲' => '于廣洲',
'于式枚' => '于式枚',
-'于從濂' => '于從濂',
'于从濂' => '于從濂',
+'于從濂' => '于從濂',
'于德海' => '于德海',
-'于志宁' => '于志寧',
'于志寧' => '于志寧',
+'于志宁' => '于志寧',
'于忠肃集' => '于忠肅集',
'于思' => '于思',
'于慎行' => '于慎行',
@@ -3583,29 +3585,28 @@ $zh2Hant = array(
'于敏中' => '于敏中',
'于斌' => '于斌',
'于斯塔德' => '于斯塔德',
-'于斯纳尔斯贝里' => '于斯納爾斯貝里',
'于斯納爾斯貝里' => '于斯納爾斯貝里',
-'于斯达尔' => '于斯達爾',
+'于斯纳尔斯贝里' => '于斯納爾斯貝里',
'于斯達爾' => '于斯達爾',
-'于明涛' => '于明濤',
+'于斯达尔' => '于斯達爾',
'于明濤' => '于明濤',
+'于明涛' => '于明濤',
'于是之' => '于是之',
'于晨楠' => '于晨楠',
'于晴' => '于晴',
-'于會泳' => '于會泳',
'于会泳' => '于會泳',
-'于根伟' => '于根偉',
+'于會泳' => '于會泳',
'于根偉' => '于根偉',
+'于根伟' => '于根偉',
'于格' => '于格',
-'于楓' => '于楓',
'于枫' => '于楓',
+'于楓' => '于楓',
'于荣光' => '于榮光',
'于樂' => '于樂',
-'于树洁' => '于樹潔',
'于樹潔' => '于樹潔',
+'于树洁' => '于樹潔',
'于欣' => '于欣',
'于欣源' => '于欣源',
-'于正升' => '于正昇',
'于正昇' => '于正昇',
'于正昌' => '于正昌',
'于归' => '于歸',
@@ -3614,25 +3615,25 @@ $zh2Hant = array(
'于江震' => '于江震',
'于波' => '于波',
'于洋' => '于洋',
-'于洪区' => '于洪區',
'于洪區' => '于洪區',
+'于洪区' => '于洪區',
'于浩威' => '于浩威',
'于海' => '于海',
'于海洋' => '于海洋',
-'于湘兰' => '于湘蘭',
'于湘蘭' => '于湘蘭',
+'于湘兰' => '于湘蘭',
'于漢超' => '于漢超',
'于汉超' => '于漢超',
'于澄' => '于澄',
-'于泽尔' => '于澤爾',
'于澤爾' => '于澤爾',
+'于泽尔' => '于澤爾',
'于涛' => '于濤',
'于濤' => '于濤',
'于熙珍' => '于熙珍',
-'于爾岑' => '于爾岑',
'于尔岑' => '于爾岑',
-'于爾根' => '于爾根',
+'于爾岑' => '于爾岑',
'于尔根' => '于爾根',
+'于爾根' => '于爾根',
'于尔里克' => '于爾里克',
'于爾里克' => '于爾里克',
'于特森' => '于特森',
@@ -3645,8 +3646,8 @@ $zh2Hant = array(
'于美人' => '于美人',
'于耘婕' => '于耘婕',
'于若木' => '于若木',
-'于蔭霖' => '于蔭霖',
'于荫霖' => '于蔭霖',
+'于蔭霖' => '于蔭霖',
'于衡' => '于衡',
'于西翰' => '于西翰',
'于謙' => '于謙',
@@ -3662,35 +3663,36 @@ $zh2Hant = array(
'于道泉' => '于道泉',
'于远伟' => '于遠偉',
'于遠偉' => '于遠偉',
-'于都縣' => '于都縣',
'于都县' => '于都縣',
+'于都縣' => '于都縣',
'于里察' => '于里察',
'于阗' => '于闐',
-'于雙戈' => '于雙戈',
'于双戈' => '于雙戈',
+'于雙戈' => '于雙戈',
'于云鹤' => '于雲鶴',
'于震' => '于震',
'于震寰' => '于震寰',
'于震环' => '于震環',
'于震環' => '于震環',
'于靖' => '于靖',
+'于非暗' => '于非闇',
'于非闇' => '于非闇',
'于韋斯屈萊' => '于韋斯屈萊',
'于韦斯屈莱' => '于韋斯屈萊',
'于风政' => '于風政',
'于風政' => '于風政',
'于飞' => '于飛',
-'于飞岛' => '于飛島',
'于飛島' => '于飛島',
+'于飞岛' => '于飛島',
'于余曲折' => '于餘曲折',
'于鬯' => '于鬯',
'于魁智' => '于魁智',
-'于凤桐' => '于鳳桐',
'于鳳桐' => '于鳳桐',
-'于凤至' => '于鳳至',
+'于凤桐' => '于鳳桐',
'于鳳至' => '于鳳至',
-'于默奥' => '于默奧',
+'于凤至' => '于鳳至',
'于默奧' => '于默奧',
+'于默奥' => '于默奧',
'云乎' => '云乎',
'云云' => '云云',
'云何' => '云何',
@@ -3700,24 +3702,19 @@ $zh2Hant = array(
'云尔' => '云爾',
'云:' => '云:',
'五个' => '五個',
-'五出刊' => '五出刊',
-'五出口' => '五出口',
-'五出版' => '五出版',
-'五出生' => '五出生',
-'五出祁山' => '五出祁山',
-'五出逃' => '五出逃',
'五周后' => '五周後',
'五天后' => '五天後',
'五岳' => '五嶽',
'五年' => '五年',
'五谷' => '五穀',
'五扎' => '五紮',
+'五脏' => '五臟',
'五行生克' => '五行生剋',
'五谷王北街' => '五谷王北街',
'五谷王南街' => '五谷王南街',
'五只' => '五隻',
'五余' => '五餘',
-'五出' => '五齣',
+'井干' => '井幹',
'井干摧败' => '井榦摧敗',
'井里' => '井裡',
'亚于' => '亞於',
@@ -3726,53 +3723,31 @@ $zh2Hant = array(
'交游' => '交遊',
'交哄' => '交鬨',
'亦云' => '亦云',
-'亦庄亦谐' => '亦莊亦諧',
'亮丑' => '亮醜',
'亮钟' => '亮鐘',
'人云' => '人云',
-'人参加' => '人參加',
-'人参展' => '人參展',
-'人参战' => '人參戰',
-'人参拜' => '人參拜',
-'人参政' => '人參政',
-'人参照' => '人參照',
-'人参看' => '人參看',
-'人参禅' => '人參禪',
-'人参考' => '人參考',
-'人参与' => '人參與',
-'人参见' => '人參見',
-'人参观' => '人參觀',
-'人参谋' => '人參謀',
-'人参议' => '人參議',
-'人参赞' => '人參贊',
-'人参透' => '人參透',
-'人参选' => '人參選',
-'人参酌' => '人參酌',
-'人参阅' => '人參閱',
'人如风后入江云' => '人如風後入江雲',
'人欲' => '人慾',
+'人数只' => '人數只',
+'人数里' => '人數裡',
'人物志' => '人物誌',
-'人参' => '人蔘',
+'人生天里' => '人生天里',
'什锦面' => '什錦麵',
-'什么' => '什麼',
'仇仇' => '仇讎',
-'今天' => '今天',
-'他克制' => '他剋制',
+'介胄' => '介冑',
'他钟' => '他鐘',
'付托' => '付託',
'仙后' => '仙后',
-'仙药' => '仙藥',
+'仙后座' => '仙后座',
+'代数里' => '代數裡',
+'代理发行' => '代理發行',
'代码表' => '代碼表',
'代表' => '代表',
'令人发指' => '令人髮指',
'以自制' => '以自制',
-'仰药' => '仰藥',
+'仲裁制' => '仲裁制',
'件钟' => '件鐘',
-'任何表演' => '任何表演',
-'任何表示' => '任何表示',
-'任何表達' => '任何表達',
-'任何表达' => '任何表達',
-'任何表' => '任何錶',
+'价川' => '价川',
'任何钟' => '任何鐘',
'任何钟表' => '任何鐘錶',
'任教于' => '任教於',
@@ -3797,270 +3772,19 @@ $zh2Hant = array(
'伴游' => '伴遊',
'似于' => '似於',
'但云' => '但云',
-'布于' => '佈於',
-'布道' => '佈道',
-'布雷、' => '佈雷、',
-'布雷。' => '佈雷。',
-'布雷封锁' => '佈雷封鎖',
-'布雷的' => '佈雷的',
-'布雷艇' => '佈雷艇',
-'布雷舰' => '佈雷艦',
-'布雷速度' => '佈雷速度',
-'布雷,' => '佈雷,',
-'布雷;' => '佈雷;',
'位于' => '位於',
'位准' => '位準',
'低洼' => '低洼',
'住扎' => '住紮',
-'占0' => '佔0',
-'占1' => '佔1',
-'占2' => '佔2',
-'占3' => '佔3',
-'占4' => '佔4',
-'占5' => '佔5',
-'占6' => '佔6',
-'占7' => '佔7',
-'占8' => '佔8',
-'占9' => '佔9',
-'占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',
-'占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',
-'占〇' => '佔〇',
-'占一' => '佔一',
-'占七' => '佔七',
-'占万' => '佔万',
-'占三' => '佔三',
-'占上风' => '佔上風',
-'占下' => '佔下',
-'占下风' => '佔下風',
-'占不占' => '佔不佔',
-'占不足' => '佔不足',
-'占世界' => '佔世界',
-'占中' => '佔中',
-'占主' => '佔主',
-'占九' => '佔九',
-'占了' => '佔了',
-'占二' => '佔二',
-'占五' => '佔五',
-'占人便宜' => '佔人便宜',
-'占位' => '佔位',
-'占住' => '佔住',
-'占占' => '佔佔',
-'占便宜' => '佔便宜',
-'占俄' => '佔俄',
-'占个' => '佔個',
-'占个位' => '佔個位',
-'占停车' => '佔停車',
-'占亿' => '佔億',
-'占优' => '佔優',
-'占先' => '佔先',
-'占光' => '佔光',
-'占全' => '佔全',
-'占两' => '佔兩',
-'占八' => '佔八',
-'占六' => '佔六',
-'占分' => '佔分',
-'占到' => '佔到',
-'占加' => '佔加',
-'占劣' => '佔劣',
-'占北' => '佔北',
-'占十' => '佔十',
-'占千' => '佔千',
-'占半' => '佔半',
-'占南' => '佔南',
-'占印' => '佔印',
-'占去' => '佔去',
-'占取' => '佔取',
-'占台' => '佔台',
-'占哺乳' => '佔哺乳',
-'占嗫' => '佔囁',
-'占四' => '佔四',
-'占国内' => '佔國內',
-'占在' => '佔在',
-'占地' => '佔地',
-'占场' => '佔場',
-'占压' => '佔壓',
-'占多' => '佔多',
-'占大' => '佔大',
-'占好' => '佔好',
-'占小' => '佔小',
-'占少' => '佔少',
-'占局部' => '佔局部',
-'占屋' => '佔屋',
-'占山' => '佔山',
-'占市场' => '佔市場',
-'占平均' => '佔平均',
-'占床' => '佔床',
-'占座' => '佔座',
-'占后' => '佔後',
-'占得' => '佔得',
-'占德' => '佔德',
-'占掉' => '佔掉',
-'占据' => '佔據',
-'占整体' => '佔整體',
-'占新' => '佔新',
-'占有' => '佔有',
-'占有欲' => '佔有慾',
-'占东' => '佔東',
-'占查' => '佔查',
-'占次' => '佔次',
-'占比' => '佔比',
-'占法' => '佔法',
-'占满' => '佔滿',
-'占澳' => '佔澳',
-'占为' => '佔為',
-'占率' => '佔率',
-'占用' => '佔用',
'占毕' => '佔畢',
-'占百' => '佔百',
-'占尽' => '佔盡',
-'占稳' => '佔穩',
-'占网' => '佔網',
-'占线' => '佔線',
-'占总' => '佔總',
-'占缺' => '佔缺',
-'占美' => '佔美',
-'占耕' => '佔耕',
-'占至多' => '佔至多',
-'占至少' => '佔至少',
-'占英' => '佔英',
-'占着' => '佔著',
-'占葡' => '佔葡',
-'占苏' => '佔蘇',
-'占西' => '佔西',
-'占资源' => '佔資源',
-'占起' => '佔起',
-'占超过' => '佔超過',
-'占过' => '佔過',
-'占道' => '佔道',
-'占零' => '佔零',
-'占領' => '佔領',
-'占领' => '佔領',
-'占头' => '佔頭',
'占头筹' => '佔頭籌',
-'占饭' => '佔飯',
-'占香' => '佔香',
-'占马' => '佔馬',
'占高枝儿' => '佔高枝兒',
-'占0' => '佔0',
-'占1' => '佔1',
-'占2' => '佔2',
-'占3' => '佔3',
-'占4' => '佔4',
-'占5' => '佔5',
-'占6' => '佔6',
-'占7' => '佔7',
-'占8' => '佔8',
-'占9' => '佔9',
-'占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',
-'占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',
'何杰' => '何杰',
-'余三胜' => '余三勝',
'余三勝' => '余三勝',
+'余三胜' => '余三勝',
'余光中' => '余光中',
'余光生' => '余光生',
-'余力為' => '余力為',
'余力为' => '余力為',
-'余姓' => '余姓',
'余威德' => '余威德',
'余子明' => '余子明',
'余思敏' => '余思敏',
@@ -4069,12 +3793,13 @@ $zh2Hant = array(
'作品里' => '作品裡',
'作奸犯科' => '作姦犯科',
'作准' => '作準',
-'作庄' => '作莊',
-'你克制' => '你剋制',
'你斗了胆' => '你斗了膽',
-'你才子发昏' => '你纔子發昏',
-'佣金收益' => '佣金收益',
-'佣金费用' => '佣金費用',
+'你夸' => '你誇',
+'佣金' => '佣金',
+'佣鈿' => '佣鈿',
+'佣钿' => '佣鈿',
+'佣钱' => '佣錢',
+'佣錢' => '佣錢',
'佳肴' => '佳肴',
'佳里鎮' => '佳里鎮',
'并一不二' => '併一不二',
@@ -4083,15 +3808,13 @@ $zh2Hant = array(
'并到' => '併到',
'并合' => '併合',
'并名' => '併名',
-'并吞下' => '併吞下',
+'并吞' => '併吞',
'并拢' => '併攏',
'并案' => '併案',
'并流' => '併流',
'并火' => '併火',
'并为一家' => '併為一家',
'并为一体' => '併為一體',
-'并产' => '併產',
-'并当' => '併當',
'并叠' => '併疊',
'并发型模式' => '併發型模式',
'并发模式' => '併發模式',
@@ -4102,76 +3825,56 @@ $zh2Hant = array(
'并线' => '併線',
'并肩子' => '併肩子',
'并购' => '併購',
-'并除' => '併除',
'并骨' => '併骨',
'使其斗' => '使其鬥',
'来于' => '來於',
-'来复' => '來複',
'侍仆' => '侍僕',
'供制' => '供製',
'依依不舍' => '依依不捨',
'依托' => '依託',
-'侵占' => '侵佔',
'侵并' => '侵併',
-'侵占到' => '侵占到',
-'侵占罪' => '侵占罪',
-'便药' => '便藥',
+'局促' => '侷促',
'系数' => '係數',
'系为' => '係為',
-'俄占' => '俄佔',
'保险柜' => '保險柜',
'信托贸易' => '信托貿易',
'信托' => '信託',
'修杰楷' => '修杰楷',
'修杰麟' => '修杰麟',
-'修炼' => '修鍊',
'修胡刀' => '修鬍刀',
'俯冲' => '俯衝',
-'个人' => '個人',
'个里' => '個裡',
'个钟' => '個鐘',
'个钟表' => '個鐘錶',
-'们克制' => '們剋制',
'们斗了胆' => '們斗了膽',
-'倒绷孩儿' => '倒繃孩兒',
'幸免' => '倖免',
'幸存' => '倖存',
'幸幸' => '倖幸',
'候复' => '候覆',
+'倚闲' => '倚閑',
'倛丑' => '倛醜',
'借听于聋' => '借聽於聾',
+'借鉴' => '借鑑',
'倦游' => '倦遊',
-'假药' => '假藥',
+'假里' => '假裡',
'假托' => '假託',
+'假里白' => '假里白',
'假发' => '假髮',
'偎干' => '偎乾',
-'做庄' => '做莊',
'停停当当' => '停停當當',
'停征' => '停徵',
'停制' => '停製',
-'偷鸡不着' => '偷雞不著',
-'伪药' => '偽藥',
'备注' => '備註',
'家伙' => '傢伙',
-'家俱' => '傢俱',
-'家具' => '傢具',
'催并' => '催併',
-'佣中佼佼' => '傭中佼佼',
-'佣人' => '傭人',
'佣仆' => '傭僕',
-'佣兵' => '傭兵',
-'佣工' => '傭工',
-'佣懒' => '傭懶',
-'佣书' => '傭書',
-'佣金' => '傭金',
'傲游' => '傲遊',
'傲霜斗雪' => '傲霜鬥雪',
-'传位于四太子' => '傳位于四太子',
'傳位于四太子' => '傳位于四太子',
+'传位于四太子' => '傳位于四太子',
'传于' => '傳於',
-'伤痕累累' => '傷痕纍纍',
+'债累累' => '債纍纍',
'傻里傻气' => '傻裡傻氣',
-'倾复' => '傾複',
'仆人' => '僕人',
'仆使' => '僕使',
'仆仆' => '僕僕',
@@ -4196,15 +3899,14 @@ $zh2Hant = array(
'雇人' => '僱人',
'雇佣' => '僱傭',
'雇到' => '僱到',
-'雇员' => '僱員',
'雇工' => '僱工',
-'雇用' => '僱用',
+'雇船' => '僱船',
+'雇请' => '僱請',
+'雇车' => '僱車',
'雇农' => '僱農',
'仪范' => '儀範',
-'仪表' => '儀錶',
'亿个' => '億個',
'亿周后' => '億周後',
-'亿多只' => '億多隻',
'亿天后' => '億天後',
'亿年' => '億年',
'亿只' => '億隻',
@@ -4219,13 +3921,14 @@ $zh2Hant = array(
'尽尽' => '儘儘',
'尽先' => '儘先',
'尽其所有' => '儘其所有',
-'尽力' => '儘力',
'尽可能' => '儘可能',
'尽快' => '儘快',
'尽早' => '儘早',
'尽是' => '儘是',
'尽管' => '儘管',
+'尽自' => '儘自',
'尽速' => '儘速',
+'尽量' => '儘量',
'优于' => '優於',
'优游' => '優遊',
'兀术' => '兀朮',
@@ -4243,9 +3946,7 @@ $zh2Hant = array(
'凶案' => '兇案',
'凶枪' => '兇槍',
'凶横' => '兇橫',
-'凶殘' => '兇殘',
'凶残' => '兇殘',
-'凶殺' => '兇殺',
'凶杀' => '兇殺',
'凶犯' => '兇犯',
'凶狠' => '兇狠',
@@ -4253,18 +3954,17 @@ $zh2Hant = array(
'凶疑' => '兇疑',
'凶相' => '兇相',
'凶险' => '兇險',
-'先占' => '先佔',
'先采' => '先採',
'光致致' => '光緻緻',
-'克药' => '克藥',
-'克复' => '克複',
'免征' => '免徵',
-'党参' => '党參',
'党太尉' => '党太尉',
+'党姓' => '党姓',
+'党家' => '党家',
'党怀英' => '党懷英',
'党进' => '党進',
-'党項' => '党項',
'党项' => '党項',
+'党項' => '党項',
+'内脏' => '內臟',
'内制' => '內製',
'内面包' => '內面包',
'内面包的' => '內面包的',
@@ -4276,20 +3976,15 @@ $zh2Hant = array(
'两个' => '兩個',
'两周后' => '兩周後',
'两天后' => '兩天後',
-'两天晒网' => '兩天晒網',
'两年' => '兩年',
+'两杆' => '兩桿',
'两扎' => '兩紮',
'两虎共斗' => '兩虎共鬥',
'两只' => '兩隻',
'两余' => '兩餘',
'两鼠斗穴' => '兩鼠鬥穴',
+'两出' => '兩齣',
'八个' => '八個',
-'八出刊' => '八出刊',
-'八出口' => '八出口',
-'八出版' => '八出版',
-'八出生' => '八出生',
-'八出祁山' => '八出祁山',
-'八出逃' => '八出逃',
'八周后' => '八周後',
'八天后' => '八天後',
'八字胡' => '八字鬍',
@@ -4304,24 +3999,19 @@ $zh2Hant = array(
'公干' => '公幹',
'公历' => '公曆',
'公历史' => '公歷史',
-'公厘' => '公釐',
+'公里海' => '公里海',
'公余' => '公餘',
+'六么' => '六么',
'六个' => '六個',
-'六出刊' => '六出刊',
-'六出口' => '六出口',
-'六出版' => '六出版',
-'六出生' => '六出生',
-'六出祁山' => '六出祁山',
-'六出逃' => '六出逃',
'六周后' => '六周後',
'六天后' => '六天後',
'六年' => '六年',
+'六楼后座' => '六樓后座',
'六谷' => '六穀',
'六扎' => '六紮',
'六冲' => '六衝',
'六只' => '六隻',
'六余' => '六餘',
-'六出' => '六齣',
'共和历' => '共和曆',
'共和历史' => '共和歷史',
'其一只' => '其一只',
@@ -4335,27 +4025,17 @@ $zh2Hant = array(
'冗余' => '冗餘',
'冤仇' => '冤讎',
'冥蒙' => '冥濛',
-'冬天里' => '冬天裡',
'冬山庄' => '冬山庄',
-'冬日里' => '冬日裡',
'冬游' => '冬遊',
-'冰冷' => '冰冷',
+'冰山里' => '冰山裡',
'冶游' => '冶遊',
-'冷庄子' => '冷莊子',
'冷面相' => '冷面相',
'冷面' => '冷麵',
'准三后' => '准三后',
-'准不准他' => '准不准他',
-'准不准你' => '准不准你',
-'准不准她' => '准不准她',
-'准不准它' => '准不准它',
-'准不准我' => '准不准我',
-'准不准许' => '准不准許',
-'准不准谁' => '准不准誰',
'准保護' => '准保護',
'准保护' => '准保護',
-'准保释' => '准保釋',
'准保釋' => '准保釋',
+'准保释' => '准保釋',
'凌蒙初' => '凌濛初',
'凝炼' => '凝鍊',
'几上' => '几上',
@@ -4369,25 +4049,21 @@ $zh2Hant = array(
'几榻' => '几榻',
'几净窗明' => '几淨窗明',
'几筵' => '几筵',
-'几丝' => '几絲',
'几面上' => '几面上',
-'凶杀案' => '凶殺案',
+'凶征' => '凶徵',
'凶相毕露' => '凶相畢露',
-'凹洞里' => '凹洞裡',
'出乖弄丑' => '出乖弄醜',
'出乖露丑' => '出乖露醜',
'出征收' => '出征收',
'出于' => '出於',
'出游' => '出遊',
'出丑' => '出醜',
-'分占' => '分佔',
+'函数里' => '函數裡',
'分别致' => '分别致',
'分半钟' => '分半鐘',
'分多钟' => '分多鐘',
'分子钟' => '分子鐘',
'分子云' => '分子雲',
-'分布圖' => '分布圖',
-'分布图' => '分布圖',
'分布于' => '分布於',
'分散于' => '分散於',
'分钟' => '分鐘',
@@ -4395,56 +4071,49 @@ $zh2Hant = array(
'划一桨' => '划一槳',
'划上' => '划上',
'划下' => '划下',
+'划不來' => '划不來',
'划不来' => '划不來',
-'划了' => '划了',
'划了一会' => '划了一會',
'划来划去' => '划來划去',
+'划來划去' => '划來划去',
'划具' => '划具',
-'划出' => '划出',
-'划到' => '划到',
'划到岸' => '划到岸',
'划到江心' => '划到江心',
'划动' => '划動',
+'划動' => '划動',
'划去' => '划去',
'划子' => '划子',
+'划得來' => '划得來',
'划得来' => '划得來',
'划拳' => '划拳',
-'划桨' => '划槳',
'划槳' => '划槳',
+'划桨' => '划槳',
'划水' => '划水',
'划算' => '划算',
'划船' => '划船',
'划艇' => '划艇',
-'划着' => '划著',
-'划着走' => '划著走',
+'划著' => '划著',
'划行' => '划行',
'划走' => '划走',
'划起' => '划起',
+'划進' => '划進',
'划进' => '划進',
-'划进来' => '划進來',
-'划进去' => '划進去',
'划过' => '划過',
-'划过来' => '划過來',
-'划过去' => '划過去',
+'划過' => '划過',
'划龙舟' => '划龍舟',
+'划龍舟' => '划龍舟',
'判断发' => '判斷發',
-'别日南鸿才北去' => '別日南鴻纔北去',
-'别致' => '別緻',
-'别庄' => '別莊',
-'别着' => '別著',
'别辟' => '別闢',
'利欲' => '利慾',
'利于' => '利於',
-'利欲熏心' => '利欲熏心',
'刮来刮去' => '刮來刮去',
-'刮着' => '刮著',
'刮起来' => '刮起來',
'刮风下雪倒便宜' => '刮風下雪倒便宜',
'刮胡' => '刮鬍',
+'到山里' => '到山裡',
'制冷机' => '制冷機',
'制签' => '制籤',
'制钟' => '制鐘',
-'刺绣' => '刺繡',
'刻半钟' => '刻半鐘',
'刻多钟' => '刻多鐘',
'刻钟' => '刻鐘',
@@ -4453,26 +4122,21 @@ $zh2Hant = array(
'剃须' => '剃鬚',
'削发' => '削髮',
'削面' => '削麵',
-'克制不了' => '剋制不了',
-'克制不住' => '剋制不住',
+'克剥' => '剋剝',
'克扣' => '剋扣',
'克星' => '剋星',
'克期' => '剋期',
'克死' => '剋死',
'克薄' => '剋薄',
-'前天' => '前天',
'前往' => '前往',
-'前言不答后语' => '前言不答後語',
'前面店' => '前面店',
-'剔庄货' => '剔莊貨',
+'剖厘' => '剖釐',
'刚干' => '剛乾',
'刚雇' => '剛僱',
-'刚才一载' => '剛纔一載',
'剥制' => '剝製',
'剩余' => '剩餘',
'剪其发' => '剪其髮',
'剪牡丹喂牛' => '剪牡丹喂牛',
-'剪彩' => '剪綵',
'剪发' => '剪髮',
'割舍' => '割捨',
'创获' => '創穫',
@@ -4484,31 +4148,28 @@ $zh2Hant = array(
'铲头' => '剷頭',
'划入' => '劃入',
'划为' => '劃為',
-'剧药' => '劇藥',
-'刘佳怜' => '劉佳怜',
'劉佳怜' => '劉佳怜',
-'刘克庄' => '劉克莊',
+'刘佳怜' => '劉佳怜',
'刘芸后' => '劉芸后',
-'力克制' => '力剋制',
'力拼' => '力拚',
'力拼众敌' => '力拼眾敵',
-'力求克制' => '力求剋制',
'力争上游' => '力爭上遊',
+'功勋' => '功勳',
'功致' => '功緻',
'加氢精制' => '加氫精制',
-'加药' => '加藥',
'加注' => '加註',
'劣于' => '劣於',
'助于' => '助於',
'劫余' => '劫餘',
'勃郁' => '勃鬱',
-'动荡' => '動蕩',
'胜于' => '勝於',
-'劳力士表' => '勞力士錶',
'勤仆' => '勤僕',
'勤朴' => '勤樸',
+'勋劳' => '勳勞',
+'勋业' => '勳業',
+'勋爵' => '勳爵',
'勋章' => '勳章',
-'勺药' => '勺藥',
+'勋绩' => '勳績',
'勾干' => '勾幹',
'勾心斗角' => '勾心鬥角',
'勾魂荡魄' => '勾魂蕩魄',
@@ -4516,24 +4177,20 @@ $zh2Hant = array(
'包准' => '包準',
'包谷' => '包穀',
'包扎' => '包紮',
-'包庄' => '包莊',
'匏系' => '匏繫',
-'化学家' => '化學家',
+'北山索面' => '北山索麵',
'北岳' => '北嶽',
'北回线' => '北迴線',
'北回铁路' => '北迴鐵路',
-'匡复' => '匡複',
'匪干' => '匪幹',
'匿于' => '匿於',
'十个' => '十個',
-'十出刊' => '十出刊',
-'十出口' => '十出口',
-'十出版' => '十出版',
+'十出家' => '十出家',
+'十出击' => '十出擊',
'十出生' => '十出生',
'十出祁山' => '十出祁山',
-'十出逃' => '十出逃',
+'十出头' => '十出頭',
'十周后' => '十周後',
-'十多只' => '十多隻',
'十天后' => '十天後',
'十年' => '十年',
'十扎' => '十紮',
@@ -4542,34 +4199,31 @@ $zh2Hant = array(
'十出' => '十齣',
'千个' => '千個',
'千只可' => '千只可',
-'千只夠' => '千只夠',
'千只够' => '千只夠',
+'千只夠' => '千只夠',
'千只怕' => '千只怕',
'千只能' => '千只能',
'千只足够' => '千只足夠',
'千只足夠' => '千只足夠',
'千周后' => '千周後',
-'千多只' => '千多隻',
'千天后' => '千天後',
'千年' => '千年',
'千扎' => '千紮',
-'千丝万缕' => '千絲萬縷',
'千回百折' => '千迴百折',
'千回百转' => '千迴百轉',
'千钧一发' => '千鈞一髮',
'千只' => '千隻',
'千余' => '千餘',
-'升官发财' => '升官發財',
'半制品' => '半制品',
'半只可' => '半只可',
'半只够' => '半只夠',
'半于' => '半於',
'半只' => '半隻',
-'协调' => '協調',
'协防' => '協防',
'南京钟' => '南京鐘',
'南京钟表' => '南京鐘錶',
'南宫适' => '南宮适',
+'南宮适' => '南宮适',
'南屏晚钟' => '南屏晚鐘',
'南岳' => '南嶽',
'南筑' => '南筑',
@@ -4578,42 +4232,34 @@ $zh2Hant = array(
'南游' => '南遊',
'博汇' => '博彙',
'博采' => '博採',
+'博尔术' => '博爾朮',
'卜云吉' => '卜云吉',
-'卞庄' => '卞莊',
-'卞庄子' => '卞莊子',
'占了卜' => '占了卜',
'占便宜的是呆' => '占便宜的是獃',
-'占卜' => '占卜',
-'占多数' => '占多數',
-'占有五不验' => '占有五不驗',
-'占有权' => '占有權',
'印累绶若' => '印纍綬若',
'印制' => '印製',
+'印鉴' => '印鑑',
'危于' => '危於',
'卵与石斗' => '卵與石鬥',
+'卷发' => '卷髮',
'卷须' => '卷鬚',
'厂部' => '厂部',
'厝薪于火' => '厝薪於火',
'原子钟' => '原子鐘',
'原钟' => '原鐘',
'历物之意' => '厤物之意',
-'参合' => '參合',
-'参考价值' => '參考價值',
-'参与' => '參與',
-'参与人员' => '參與人員',
-'参与制' => '參與制',
-'参与感' => '參與感',
-'参与者' => '參與者',
-'参观团' => '參觀團',
-'参观团体' => '參觀團體',
-'参阅' => '參閱',
+'去山里' => '去山裡',
+'参数只' => '參數只',
+'参数里' => '參數裡',
'反朴' => '反樸',
'反冲' => '反衝',
'反复制' => '反複製',
-'反复' => '反覆',
'反覆' => '反覆',
+'反复' => '反覆',
'取舍' => '取捨',
+'受雇' => '受僱',
'受托' => '受託',
+'丛林里' => '叢林裡',
'口干' => '口乾',
'口干冒' => '口干冒',
'口干政' => '口干政',
@@ -4623,25 +4269,24 @@ $zh2Hant = array(
'口燥唇干' => '口燥唇乾',
'口腹之欲' => '口腹之慾',
'口里' => '口裡',
-'口试' => '口試',
'口钟' => '口鐘',
-'古书云' => '古書云',
'古書云' => '古書云',
+'古书云' => '古書云',
'古柯咸' => '古柯鹹',
-'古柯碱' => '古柯鹼',
'古朴' => '古樸',
'古语云' => '古語云',
'古語云' => '古語云',
-'古迹' => '古迹',
+'古迹' => '古蹟',
'古钟' => '古鐘',
'古钟表' => '古鐘錶',
'另辟' => '另闢',
'叩钟' => '叩鐘',
-'只占' => '只佔',
'只占卜' => '只占卜',
'只占吉' => '只占吉',
'只占神问卜' => '只占神問卜',
'只占算' => '只占算',
+'只影响' => '只影響',
+'只影響' => '只影響',
'只采' => '只採',
'只冲' => '只衝',
'只要功夫深,铁杵磨成锈花针' => '只要功夫深,鐵杵磨成鏽花針',
@@ -4659,76 +4304,58 @@ $zh2Hant = array(
'只身子' => '只身子',
'只身形' => '只身形',
'只身影' => '只身影',
-'只身后' => '只身後',
'只身後' => '只身後',
+'只身后' => '只身後',
'只身心' => '只身心',
'只身旁' => '只身旁',
'只身材' => '只身材',
'只身段' => '只身段',
'只身为' => '只身為',
'只身為' => '只身為',
-'只身边' => '只身邊',
'只身邊' => '只身邊',
+'只身边' => '只身邊',
'只身首' => '只身首',
-'只身体' => '只身體',
'只身體' => '只身體',
+'只身体' => '只身體',
'只身高' => '只身高',
'只采声' => '只采聲',
'叮叮当当' => '叮叮噹噹',
'叮当' => '叮噹',
-'可以克制' => '可以剋制',
'可紧可松' => '可緊可鬆',
'可自制' => '可自制',
+'可鉴' => '可鑑',
'台子女' => '台子女',
'台子孙' => '台子孫',
'台州' => '台州',
'台布景' => '台布景',
'台历史' => '台歷史',
'台钟' => '台鐘',
-'台面前' => '台面前',
-'叱咤903' => '叱咤903',
-'叱咤MY903' => '叱咤MY903',
-'叱咤My903' => '叱咤My903',
-'叱咤叱叱咤' => '叱咤叱叱咤',
-'叱咤叱咤叱咤咤' => '叱咤叱咤叱咤咤',
-'叱咤咤' => '叱咤咤',
-'叱咤乐坛' => '叱咤樂壇',
-'叱咤樂壇' => '叱咤樂壇',
-'叶 恭弘' => '叶 恭弘',
-'叶 恭弘' => '叶 恭弘',
+'台风奖' => '台風獎',
+'台风稳健' => '台風穩健',
+'史鉴' => '史鑑',
'叶不二子' => '叶不二子',
'叶志穗' => '叶志穗',
'叶恭弘' => '叶恭弘',
'叶音' => '叶音',
'叶韵' => '叶韻',
'吃板刀面' => '吃板刀麵',
-'吃着不尽' => '吃著不盡',
'吃姜' => '吃薑',
-'吃药' => '吃藥',
'吃里扒外' => '吃裡扒外',
'吃里爬外' => '吃裡爬外',
-'吃辣面' => '吃辣麵',
-'吃错药' => '吃錯藥',
'各辟' => '各闢',
'各类钟' => '各類鐘',
'合伙人' => '合伙人',
+'合并' => '合併',
'合伙' => '合夥',
-'合并' => '合并',
'合府上' => '合府上',
'合采' => '合採',
'合历' => '合曆',
'合历史' => '合歷史',
'合准' => '合準',
-'合着' => '合著',
-'合著者' => '合著者',
'吉凶庆吊' => '吉凶慶弔',
-'吊带裤' => '吊帶褲',
-'吊挂着' => '吊掛著',
-'吊杆' => '吊杆',
-'吊着' => '吊著',
-'吊裤' => '吊褲',
-'吊裤带' => '吊褲帶',
+'吉征' => '吉徵',
'吊钟' => '吊鐘',
+'同人志' => '同人誌',
'同伙' => '同夥',
'同于' => '同於',
'同余' => '同餘',
@@ -4739,22 +4366,24 @@ $zh2Hant = array(
'后姓' => '后姓',
'后安路' => '后安路',
'后平路' => '后平路',
+'后庄' => '后庄',
'后座' => '后座',
'后母戊' => '后母戊',
-'后海湾' => '后海灣',
'后海灣' => '后海灣',
+'后海湾' => '后海灣',
'后瑞站' => '后瑞站',
'后稷' => '后稷',
+'后綜' => '后綜',
'后羿' => '后羿',
'后街' => '后街',
'后角' => '后角',
-'后丰' => '后豐',
'后豐' => '后豐',
+'后丰' => '后豐',
'后里' => '后里',
-'后发FK型星' => '后髮FK型星',
'后髮FK型星' => '后髮FK型星',
-'后髮座' => '后髮座',
+'后发FK型星' => '后髮FK型星',
'后发座' => '后髮座',
+'后髮座' => '后髮座',
'后发星系团' => '后髮星系團',
'后髮星系團' => '后髮星系團',
'吐哺捉发' => '吐哺捉髮',
@@ -4763,9 +4392,9 @@ $zh2Hant = array(
'向往常' => '向往常',
'向往日' => '向往日',
'向往时' => '向往時',
-'向着' => '向著',
'吞并' => '吞併',
'吟游' => '吟遊',
+'吧台' => '吧檯',
'含齿戴发' => '含齒戴髮',
'吹干' => '吹乾',
'吹发' => '吹髮',
@@ -4773,42 +4402,31 @@ $zh2Hant = array(
'吾为之范我驰驱' => '吾爲之範我馳驅',
'吕后' => '呂后',
'呂后' => '呂后',
-'呆呆傻傻' => '呆呆傻傻',
-'呆呆挣挣' => '呆呆掙掙',
-'呆呆獸' => '呆呆獸',
'呆呆兽' => '呆呆獸',
-'呆呆笨笨' => '呆呆笨笨',
'呆致致' => '呆緻緻',
'呆里呆气' => '呆裡呆氣',
-'周一' => '周一',
-'周三' => '周三',
-'周二' => '周二',
-'周五' => '周五',
-'周六' => '周六',
+'告札' => '告劄',
'周后' => '周后',
-'周四' => '周四',
'周历' => '周曆',
-'周杰伦' => '周杰倫',
'周杰倫' => '周杰倫',
+'周杰伦' => '周杰倫',
'周历史' => '周歷史',
-'周庄王' => '周莊王',
'周游' => '周遊',
'呼吁' => '呼籲',
'命中注定' => '命中注定',
-'和克制' => '和剋制',
'和奸' => '和姦',
'咎征' => '咎徵',
'咕咕钟' => '咕咕鐘',
+'咪表' => '咪錶',
'咬姜呷醋' => '咬薑呷醋',
'咯当' => '咯噹',
-'咳嗽药' => '咳嗽藥',
'哀吊' => '哀弔',
'哀挽' => '哀輓',
'品汇' => '品彙',
+'品鉴' => '品鑑',
'哄堂大笑' => '哄堂大笑',
-'员山庄' => '員山庄',
+'員山庄' => '員山庄',
'哪里' => '哪裡',
-'哭脏' => '哭髒',
'唁吊' => '唁弔',
'呗赞' => '唄讚',
'唇干' => '唇乾',
@@ -4817,6 +4435,7 @@ $zh2Hant = array(
'唾面自干' => '唾面自乾',
'唾余' => '唾餘',
'商历' => '商曆',
+'商标准许' => '商標准許',
'商历史' => '商歷史',
'启发式' => '啟發式',
'啷当' => '啷噹',
@@ -4827,15 +4446,15 @@ $zh2Hant = array(
'喜欢钟' => '喜歡鐘',
'喜欢钟表' => '喜歡鐘錶',
'喝干' => '喝乾',
+'喧哗' => '喧譁',
'喧哄' => '喧鬨',
'丧钟' => '喪鐘',
'乔岳' => '喬嶽',
+'單于' => '單于',
'单于' => '單于',
'单单于' => '單單於',
'单干' => '單幹',
'单打独斗' => '單打獨鬥',
-'嗑药' => '嗑藥',
-'嘀嗒的表' => '嘀嗒的錶',
'嘉谷' => '嘉穀',
'嘉肴' => '嘉肴',
'嘴里' => '嘴裡',
@@ -4845,25 +4464,21 @@ $zh2Hant = array(
'当啷' => '噹啷',
'当当' => '噹噹',
'噜苏' => '嚕囌',
+'啮合' => '嚙合',
+'啮齿类' => '嚙齒類',
'向导' => '嚮導',
'向往' => '嚮往',
-'向应' => '嚮應',
+'向慕' => '嚮慕',
'向迩' => '嚮邇',
'严云农' => '嚴云農',
'严于' => '嚴於',
-'严丝合缝' => '嚴絲合縫',
'嚼谷' => '嚼穀',
'啰啰苏苏' => '囉囉囌囌',
'啰苏' => '囉囌',
'嘱托' => '囑託',
+'啮虫' => '囓蟲',
'四个' => '四個',
-'四出刊' => '四出刊',
-'四出口' => '四出口',
'四出征收' => '四出徵收',
-'四出版' => '四出版',
-'四出生' => '四出生',
-'四出祁山' => '四出祁山',
-'四出逃' => '四出逃',
'四分历' => '四分曆',
'四分历史' => '四分歷史',
'四周后' => '四周後',
@@ -4871,21 +4486,25 @@ $zh2Hant = array(
'四年' => '四年',
'四舍五入' => '四捨五入',
'四舍六入' => '四捨六入',
+'四杆铁笔' => '四桿鐵筆',
'四扎' => '四紮',
'四只' => '四隻',
'四面包' => '四面包',
'四面钟' => '四面鐘',
'四余' => '四餘',
-'四出' => '四齣',
+'回佣' => '回佣',
'回采' => '回採',
'回旋加速' => '回旋加速',
'回历' => '回曆',
'回历史' => '回歷史',
-'回丝' => '回絲',
-'回着' => '回著',
-'回荡' => '回蕩',
+'回复中' => '回覆中',
+'回复你' => '回覆你',
+'回复帖子' => '回覆帖子',
+'回复意见' => '回覆意見',
+'回复说' => '回覆說',
+'回复邮件' => '回覆郵件',
+'回复:' => '回覆:',
'回游' => '回遊',
-'回阳荡气' => '回陽蕩氣',
'因于' => '因於',
'困倦起来' => '困倦起來',
'困兽之斗' => '困獸之鬥',
@@ -4894,8 +4513,6 @@ $zh2Hant = array(
'固定制' => '固定制',
'固征' => '固徵',
'囿于' => '囿於',
-'圈占' => '圈佔',
-'圈子里' => '圈子裡',
'圈梁' => '圈樑',
'圈里' => '圈裡',
'国之桢干' => '國之楨榦',
@@ -4910,23 +4527,23 @@ $zh2Hant = array(
'国仇' => '國讎',
'园里' => '園裡',
'园游会' => '園遊會',
-'图里' => '圖裡',
+'图里的' => '圖裡的',
+'图里,' => '圖裡,',
'图鉴' => '圖鑑',
+'土索面' => '土索麵',
'土里' => '土裡',
'土制' => '土製',
-'土霉素' => '土霉素',
'在制品' => '在制品',
-'在克制' => '在剋制',
+'在山里' => '在山裡',
'在于' => '在於',
-'地占' => '地佔',
-'地克制' => '地剋制',
+'地图里' => '地圖裡',
'地心历表' => '地心曆表',
'地方志' => '地方志',
'地志' => '地誌',
'地丑德齐' => '地醜德齊',
'坏于' => '坏於',
'坐如钟' => '坐如鐘',
-'坐庄' => '坐莊',
+'坐台' => '坐檯',
'坐钟' => '坐鐘',
'坑里' => '坑裡',
'坤范' => '坤範',
@@ -4941,59 +4558,55 @@ $zh2Hant = array(
'埃及历史' => '埃及歷史',
'埃及艳后' => '埃及豔后',
'埃荣冲' => '埃榮衝',
-'埋头寻表' => '埋頭尋錶',
-'埋头寻钟' => '埋頭尋鐘',
-'埋头寻钟表' => '埋頭尋鐘錶',
'城里' => '城裡',
-'埔裡社撫墾局' => '埔裏社撫墾局',
-'埔裏社撫墾局' => '埔裏社撫墾局',
-'埔里社抚垦局' => '埔裏社撫墾局',
+'埔子里' => '埔子里',
+'埔里社' => '埔裏社',
+'域里' => '域裡',
'基干' => '基幹',
'基于' => '基於',
'基准' => '基準',
'坚致' => '堅緻',
'堙淀' => '堙澱',
-'涂着' => '塗著',
-'涂药' => '塗藥',
+'堡子里' => '堡子里',
+'场里' => '場裡',
'塞耳盗钟' => '塞耳盜鐘',
-'塞药' => '塞藥',
'墓志铭' => '墓志銘',
'墓志' => '墓誌',
'增辟' => '增闢',
+'墨子里' => '墨子里',
'墨沈' => '墨沈',
'墨沈未干' => '墨瀋未乾',
-'堕胎药' => '墮胎藥',
-'垦复' => '墾複',
'垦辟' => '墾闢',
-'垄断价格' => '壟斷價格',
-'垄断资产' => '壟斷資產',
-'垄断集团' => '壟斷集團',
'壮游' => '壯遊',
'壮面' => '壯麵',
'壹郁' => '壹鬱',
'壶里' => '壺裡',
'壸范' => '壼範',
+'壽天里' => '壽天里',
'寿面' => '壽麵',
'夏于乔' => '夏于喬',
'夏于喬' => '夏于喬',
-'夏天里' => '夏天裡',
-'夏日里' => '夏日裡',
'夏历' => '夏曆',
'夏历史' => '夏歷史',
'夏游' => '夏遊',
'外强中干' => '外強中乾',
'外制' => '外製',
-'多占' => '多佔',
'多半只' => '多半只',
+'多只包括' => '多只包括',
'多只可' => '多只可',
+'多只含' => '多只含',
'多只在' => '多只在',
'多只是' => '多只是',
'多只會' => '多只會',
'多只会' => '多只會',
'多只有' => '多只有',
+'多只比' => '多只比',
'多只用' => '多只用',
'多只能' => '多只能',
+'多只限' => '多只限',
'多只需' => '多只需',
+'多只须' => '多只須',
+'多只須' => '多只須',
'多周后' => '多周後',
'多天后' => '多天後',
'多于' => '多於',
@@ -5001,13 +4614,10 @@ $zh2Hant = array(
'多丑' => '多醜',
'多只' => '多隻',
'多余' => '多餘',
-'多么' => '多麼',
-'夜光表' => '夜光錶',
+'多出电影' => '多齣電影',
+'夜晚里' => '夜晚裡',
'夜里' => '夜裡',
'夜游' => '夜遊',
-'够克制' => '夠剋制',
-'夢有五不占' => '夢有五不占',
-'梦有五不占' => '夢有五不占',
'梦里' => '夢裡',
'梦游' => '夢遊',
'伙伴' => '夥伴',
@@ -5015,7 +4625,6 @@ $zh2Hant = array(
'伙同' => '夥同',
'伙众' => '夥眾',
'伙计' => '夥計',
-'大丑' => '大丑',
'大伙儿' => '大伙兒',
'大只可' => '大只可',
'大只在' => '大只在',
@@ -5029,6 +4638,7 @@ $zh2Hant = array(
'大型钟表面' => '大型鐘表面',
'大型钟表' => '大型鐘錶',
'大型钟面' => '大型鐘面',
+'大多只' => '大多只',
'大伙' => '大夥',
'大干' => '大幹',
'大批涌到' => '大批湧到',
@@ -5039,7 +4649,6 @@ $zh2Hant = array(
'大本钟' => '大本鐘',
'大本钟敲' => '大本鐘敲',
'大历史' => '大歷史',
-'大呆' => '大獃',
'大病初愈' => '大病初癒',
'大目干连' => '大目乾連',
'大笨钟' => '大笨鐘',
@@ -5048,14 +4657,15 @@ $zh2Hant = array(
'大衍历' => '大衍曆',
'大衍历史' => '大衍歷史',
'大言非夸' => '大言非夸',
+'大夸' => '大誇',
'大赞' => '大讚',
'大周折' => '大週摺',
+'大丑' => '大醜',
'大金发苔' => '大金髮苔',
'大钟' => '大鐘',
'大只' => '大隻',
'大风后' => '大風後',
-'大曲' => '大麴',
-'天干物燥' => '天乾物燥',
+'大曲酒' => '大麴酒',
'天克地冲' => '天克地衝',
'天后' => '天后',
'天后宫' => '天后宮',
@@ -5065,47 +4675,44 @@ $zh2Hant = array(
'天后来' => '天後來',
'天后半' => '天後半',
'天后天' => '天後天',
-'天文学家' => '天文學家',
'天文学钟' => '天文學鐘',
'天文历表' => '天文曆表',
'天文钟' => '天文鐘',
'天历' => '天曆',
'天历史' => '天歷史',
-'天然碱' => '天然鹼',
-'天翻地覆' => '天翻地覆',
-'天覆地载' => '天覆地載',
+'天神之后' => '天神之后',
+'天里' => '天裡',
+'天里昂' => '天里昂',
+'天里村' => '天里村',
'太仆' => '太僕',
+'太凶' => '太兇',
'太初历' => '太初曆',
'太初历史' => '太初歷史',
'太后' => '太后',
+'太丑' => '太醜',
+'太阁' => '太閤',
'夯干' => '夯幹',
'夸人' => '夸人',
'夸克' => '夸克',
-'夸夸其谈' => '夸夸其談',
'夸姣' => '夸姣',
'夸容' => '夸容',
-'夸毗' => '夸毗',
'夸父' => '夸父',
'夸特' => '夸特',
'夸脱' => '夸脫',
-'夸诞' => '夸誕',
-'夸诞不经' => '夸誕不經',
'夸丽' => '夸麗',
+'奇勋' => '奇勳',
'奇迹' => '奇蹟',
'奇丑' => '奇醜',
'奏折' => '奏摺',
-'奥占' => '奧佔',
'夺斗' => '奪鬥',
'奋斗' => '奮鬥',
'女丑' => '女丑',
-'女佣人' => '女佣人',
-'女佣' => '女傭',
'女仆' => '女僕',
'奴仆' => '奴僕',
'奸淫掳掠' => '奸淫擄掠',
-'她克制' => '她剋制',
'好干' => '好乾',
'好家伙' => '好傢夥',
+'好凶' => '好兇',
'好勇斗狠' => '好勇鬥狠',
'好斗大' => '好斗大',
'好斗室' => '好斗室',
@@ -5115,14 +4722,15 @@ $zh2Hant = array(
'好斗膽' => '好斗膽',
'好斗蓬' => '好斗蓬',
'好于' => '好於',
-'好呆' => '好獃',
'好困' => '好睏',
'好签' => '好籤',
'好丑' => '好醜',
'好斗' => '好鬥',
'如果干' => '如果幹',
+'如饥似渴' => '如饑似渴',
'妖后' => '妖后',
-'妙药' => '妙藥',
+'妖气冲天' => '妖氣衝天',
+'妆台' => '妝檯',
'始于' => '始於',
'委托' => '委託',
'委托书' => '委託書',
@@ -5132,42 +4740,43 @@ $zh2Hant = array(
'奸宄' => '姦宄',
'奸情' => '姦情',
'奸杀' => '姦殺',
-'奸污' => '姦汙',
+'奸污' => '姦污',
'奸淫' => '姦淫',
'奸猾' => '姦猾',
'奸细' => '姦細',
'奸邪' => '姦邪',
'威棱' => '威稜',
'婢仆' => '婢僕',
-'娲杆' => '媧杆',
'嫁祸于' => '嫁禍於',
'嫌凶' => '嫌兇',
'嫌好道丑' => '嫌好道醜',
+'嫩姜' => '嫩薑',
'嬉游' => '嬉遊',
'嬖幸' => '嬖倖',
'嬴余' => '嬴餘',
'子之丰兮' => '子之丰兮',
'子云' => '子云',
-'子晳' => '子晳',
+'子里' => '子裡',
+'子里甲' => '子里甲',
'字汇' => '字彙',
'字码表' => '字碼表',
'字里行间' => '字裡行間',
'存十一于千百' => '存十一於千百',
'存折' => '存摺',
'存于' => '存於',
+'孛里海' => '孛里海',
'孤寡不谷' => '孤寡不穀',
+'学家' => '學家',
'学里' => '學裡',
'宇宙志' => '宇宙誌',
'安于' => '安於',
'安沈铁路' => '安瀋鐵路',
-'安眠药' => '安眠藥',
-'安胎药' => '安胎藥',
+'宋王台' => '宋王臺',
'宗周钟' => '宗周鐘',
'官不怕大只怕管' => '官不怕大只怕管',
'官地为采' => '官地為寀',
'官历' => '官曆',
'官历史' => '官歷史',
-'官庄' => '官莊',
'定于' => '定於',
'定准' => '定準',
'定制' => '定製',
@@ -5178,12 +4787,6 @@ $zh2Hant = array(
'害于' => '害於',
'宴游' => '宴遊',
'家仆' => '家僕',
-'家具备' => '家具備',
-'家具有' => '家具有',
-'家具木工科' => '家具木工科',
-'家具行' => '家具行',
-'家具体' => '家具體',
-'家庄' => '家莊',
'家里' => '家裡',
'家丑' => '家醜',
'容于' => '容於',
@@ -5195,52 +4798,47 @@ $zh2Hant = array(
'寇准' => '寇準',
'寇仇' => '寇讎',
'富余' => '富餘',
-'寒假里' => '寒假裡',
'寒栗' => '寒慄',
'寒于' => '寒於',
'寓于' => '寓於',
-'寡占' => '寡佔',
'寡欲' => '寡慾',
'实干' => '實幹',
+'实累累' => '實纍纍',
'写字台' => '寫字檯',
'宽宽松松' => '寬寬鬆鬆',
'宽于' => '寬於',
'宽余' => '寬餘',
'宽松' => '寬鬆',
'寮采' => '寮寀',
-'宝山庄' => '寶山庄',
-'寶曆' => '寶曆',
+'寶山庄' => '寶山庄',
'宝历' => '寶曆',
+'寶曆' => '寶曆',
'宝历史' => '寶歷史',
-'宝庄' => '寶莊',
'宝里宝气' => '寶裡寶氣',
+'宝鉴' => '寶鑑',
'寸发千金' => '寸髮千金',
'寺钟' => '寺鐘',
'封后' => '封后',
+'封为后' => '封為后',
'封面里' => '封面裡',
'射雕' => '射鵰',
-'将占' => '將佔',
-'将占卜' => '將占卜',
'专向往' => '專向往',
-'专注' => '專註',
'专辑里' => '專輯裡',
'尊后' => '尊后',
+'对不准' => '對不準',
'对折' => '對摺',
'对于' => '對於',
'对准' => '對準',
'对准表' => '對準錶',
'对准钟' => '對準鐘',
'对准钟表' => '對準鐘錶',
-'对华发动' => '對華發動',
+'对华发' => '對華發',
'对表中' => '對表中',
'对表扬' => '對表揚',
'对表明' => '對表明',
-'对表格' => '對表格',
'对表演' => '對表演',
'对表现' => '對表現',
-'对表示' => '對表示',
'对表达' => '對表達',
-'对表' => '對錶',
'导游' => '導遊',
'小丑' => '小丑',
'小价' => '小价',
@@ -5258,12 +4856,9 @@ $zh2Hant = array(
'小型钟表面' => '小型鐘表面',
'小型钟表' => '小型鐘錶',
'小型钟面' => '小型鐘面',
-'小伙子' => '小夥子',
'小米面' => '小米麵',
'小只' => '小隻',
-'少占' => '少佔',
'少采' => '少採',
-'就克制' => '就剋制',
'就范' => '就範',
'就里' => '就裡',
'尸位素餐' => '尸位素餐',
@@ -5278,9 +4873,8 @@ $zh2Hant = array(
'尸谏' => '尸諫',
'尸魂界' => '尸魂界',
'尸鸠' => '尸鳩',
+'局促不安' => '局促不安',
'局里' => '局裡',
-'屁股大吊了心' => '屁股大弔了心',
-'屋子里' => '屋子裡',
'屋梁' => '屋樑',
'屋里' => '屋裡',
'屏风后' => '屏風後',
@@ -5290,30 +4884,35 @@ $zh2Hant = array(
'属托' => '屬託',
'屯扎' => '屯紮',
'屯里' => '屯裡',
+'山仔后' => '山仔后',
'山崩钟应' => '山崩鐘應',
'山岳' => '山嶽',
'山梁' => '山樑',
-'山洞里' => '山洞裡',
'山棱' => '山稜',
'山羊胡' => '山羊鬍',
-'山庄' => '山莊',
-'山药' => '山藥',
-'山里' => '山裡',
+'山里有' => '山裡有',
+'山里的' => '山裡的',
'山谷道' => '山谷道',
'山重水复' => '山重水複',
'岱岳' => '岱嶽',
+'峇里海' => '峇里海',
'峰回' => '峰迴',
'峻岭' => '峻岭',
'昆剧' => '崑劇',
+'崑剧' => '崑劇',
+'崑山' => '崑山',
'昆山' => '崑山',
+'昆冈' => '崑岡',
'昆仑' => '崑崙',
-'昆仑山脉' => '崑崙山脈',
+'崑曲' => '崑曲',
'昆曲' => '崑曲',
'昆腔' => '崑腔',
+'崑腔' => '崑腔',
'昆苏' => '崑蘇',
+'崑苏' => '崑蘇',
'昆调' => '崑調',
+'崑调' => '崑調',
'崖广' => '崖广',
-'仑背' => '崙背',
'嶒棱' => '嶒稜',
'岳岳' => '嶽嶽',
'岳麓' => '嶽麓',
@@ -5321,28 +4920,26 @@ $zh2Hant = array(
'巡回医疗' => '巡回醫療',
'巡回' => '巡迴',
'巡游' => '巡遊',
+'工作台' => '工作檯',
'工致' => '工緻',
'左冲右突' => '左衝右突',
'巧妇做不得无面馎饦' => '巧婦做不得無麵餺飥',
'巧干' => '巧幹',
'巧历' => '巧曆',
'巧历史' => '巧歷史',
+'巨制' => '巨製',
'差之毫厘' => '差之毫厘',
-'差之毫厘,谬以千里' => '差之毫釐,謬以千里',
'差于' => '差於',
'己丑' => '己丑',
-'已占' => '已佔',
'已占卜' => '已占卜',
'已占算' => '已占算',
'巴尔干' => '巴爾幹',
'巷里' => '巷裡',
-'市占' => '市佔',
-'市占率' => '市佔率',
'市里' => '市裡',
'布谷' => '布穀',
+'布谷鸟' => '布穀鳥',
'布谷鸟钟' => '布穀鳥鐘',
-'布庄' => '布莊',
-'布谷鸟' => '布谷鳥',
+'布里海' => '布里海',
'希伯来历' => '希伯來曆',
'希伯来历史' => '希伯來歷史',
'帘子' => '帘子',
@@ -5350,40 +4947,34 @@ $zh2Hant = array(
'帝后台' => '帝后臺',
'师范' => '師範',
'席卷' => '席捲',
-'带团参加' => '帶團參加',
'带征' => '帶徵',
'带发修行' => '帶髮修行',
-'帮佣' => '幫傭',
+'幅图里' => '幅圖裡',
'干系' => '干係',
-'干着急' => '干著急',
'平平当当' => '平平當當',
-'平泉庄' => '平泉莊',
'平准' => '平準',
'年代里' => '年代裡',
'年历' => '年曆',
'年历史' => '年歷史',
'年谷' => '年穀',
'年里' => '年裡',
+'年鉴' => '年鑑',
'并力' => '并力',
-'并吞' => '并吞',
'并州' => '并州',
'并日而食' => '并日而食',
'并迭' => '并迭',
'幸免于难' => '幸免於難',
'幸于' => '幸於',
'幸运胡' => '幸運鬍',
-'干上' => '幹上',
'干下去' => '幹下去',
'干不了' => '幹不了',
'干不成' => '幹不成',
-'干了' => '幹了',
'干事' => '幹事',
'干些' => '幹些',
'干人' => '幹人',
'干什么' => '幹什麼',
'干个' => '幹個',
'干劲' => '幹勁',
-'干劲冲天' => '幹勁沖天',
'干吏' => '幹吏',
'干员' => '幹員',
'干啥' => '幹啥',
@@ -5393,7 +4984,7 @@ $zh2Hant = array(
'干完' => '幹完',
'干家' => '幹家',
'干将' => '幹將',
-'干得' => '幹得',
+'干得了' => '幹得了',
'干性油' => '幹性油',
'干才' => '幹才',
'干掉' => '幹掉',
@@ -5410,7 +5001,6 @@ $zh2Hant = array(
'干当' => '幹當',
'干的停当' => '幹的停當',
'干细胞' => '幹細胞',
-'干細胞' => '幹細胞',
'干线' => '幹線',
'干练' => '幹練',
'干缺' => '幹缺',
@@ -5430,11 +5020,11 @@ $zh2Hant = array(
'几个' => '幾個',
'几周后' => '幾周後',
'几天后' => '幾天後',
+'几进几出' => '幾進幾出',
'几只' => '幾隻',
'几出' => '幾齣',
'广部' => '广部',
-'庄稼人' => '庄稼人',
-'庄稼院' => '庄稼院',
+'床席' => '床蓆',
'店里' => '店裡',
'府干卿' => '府干卿',
'府干擾' => '府干擾',
@@ -5446,55 +5036,46 @@ $zh2Hant = array(
'府干预' => '府干預',
'府干' => '府幹',
'座钟' => '座鐘',
-'康庄大道' => '康庄大道',
-'康庄' => '康莊',
+'廍子里' => '廍子里',
+'廓子里' => '廓子里',
'厨余' => '廚餘',
'厮斗' => '廝鬥',
'庙里' => '廟裡',
-'废后' => '廢后',
'廢后' => '廢后',
+'废后' => '廢后',
'广征' => '廣徵',
'广舍' => '廣捨',
'延历' => '延曆',
'建于' => '建於',
'弄干' => '弄乾',
'弄丑' => '弄醜',
-'弄脏' => '弄髒',
+'弄脏胸' => '弄髒胸',
'弄松' => '弄鬆',
'弄鬼吊猴' => '弄鬼弔猴',
-'吊儿郎当' => '弔兒郎當',
'吊卷' => '弔卷',
'吊取' => '弔取',
'吊古' => '弔古',
-'吊古寻幽' => '弔古尋幽',
'吊唁' => '弔唁',
'吊问' => '弔問',
'吊喉' => '弔喉',
'吊丧' => '弔喪',
-'吊丧问疾' => '弔喪問疾',
'吊喭' => '弔喭',
-'吊场' => '弔場',
'吊奠' => '弔奠',
'吊孝' => '弔孝',
'吊客' => '弔客',
'吊宴' => '弔宴',
'吊带' => '弔帶',
'吊影' => '弔影',
+'吊恤' => '弔恤',
'吊慰' => '弔慰',
'吊扣' => '弔扣',
'吊拷' => '弔拷',
-'吊拷绷扒' => '弔拷繃扒',
'吊挂' => '弔掛',
'吊撒' => '弔撒',
'吊文' => '弔文',
'吊旗' => '弔旗',
-'吊书' => '弔書',
-'吊桥' => '弔橋',
'吊死' => '弔死',
-'吊死问孤' => '弔死問孤',
-'吊死问疾' => '弔死問疾',
'吊民' => '弔民',
-'吊民伐罪' => '弔民伐罪',
'吊祭' => '弔祭',
'吊纸' => '弔紙',
'吊者大悦' => '弔者大悅',
@@ -5503,28 +5084,23 @@ $zh2Hant = array(
'吊膀子' => '弔膀子',
'吊词' => '弔詞',
'吊诡' => '弔詭',
-'吊诡矜奇' => '弔詭矜奇',
'吊谎' => '弔謊',
'吊贺迎送' => '弔賀迎送',
'吊头' => '弔頭',
-'吊颈' => '弔頸',
'吊鹤' => '弔鶴',
'引斗' => '引鬥',
'弘历' => '弘曆',
'弘历史' => '弘歷史',
'弱于' => '弱於',
'弱水三千只取一瓢' => '弱水三千只取一瓢',
-'弱碱' => '弱鹼',
-'张三丰' => '張三丰',
'張三丰' => '張三丰',
+'张三丰' => '張三丰',
'张勋' => '張勳',
'张乐于张徐' => '張樂于張徐',
-'强占' => '強佔',
'强制作用' => '強制作用',
'强奸' => '強姦',
'强干' => '強幹',
'强于' => '強於',
-'强碱' => '強鹼',
'别口气' => '彆口氣',
'别强' => '彆強',
'别扭' => '彆扭',
@@ -5532,7 +5108,6 @@ $zh2Hant = array(
'别气' => '彆氣',
'弹子台' => '彈子檯',
'弹珠台' => '彈珠檯',
-'弹药' => '彈藥',
'汇刊' => '彙刊',
'汇报' => '彙報',
'汇整' => '彙整',
@@ -5542,37 +5117,40 @@ $zh2Hant = array(
'汇辑' => '彙輯',
'汇集' => '彙集',
'形单影只' => '形單影隻',
-'形影相吊' => '形影相弔',
'形于' => '形於',
'彭于晏' => '彭于晏',
'影后' => '影后',
-'仿佛' => '彷彿',
+'影相吊' => '影相弔',
'役于' => '役於',
-'彼此克制' => '彼此剋制',
'往日無仇' => '往日無讎',
'往里' => '往裡',
-'往复' => '往複',
'待复' => '待覆',
'很干' => '很乾',
'很凶' => '很兇',
+'很准' => '很準',
'很丑' => '很醜',
'律历志' => '律曆志',
'后印' => '後印',
'后台老板' => '後台老板',
'后天' => '後天',
-'后庄' => '後庄',
'后面店' => '後面店',
'徐干' => '徐幹',
+'徒杠' => '徒杠',
'徒托空言' => '徒託空言',
-'得克制' => '得剋制',
+'得到回复' => '得到回覆',
+'从仆' => '從僕',
+'从图里' => '從圖裡',
+'从山里' => '從山裡',
'从于' => '從於',
'从里到外' => '從裡到外',
'从里向外' => '從裡向外',
+'御岳山' => '御嶽山',
+'御制' => '御製',
'复始' => '復始',
'复活节历表' => '復活節曆表',
+'复苏' => '復甦',
'征人' => '徵人',
'征令' => '徵令',
-'征占' => '徵佔',
'征信' => '徵信',
'征候' => '徵候',
'征兆' => '徵兆',
@@ -5620,7 +5198,6 @@ $zh2Hant = array(
'征集' => '徵集',
'征风召雨' => '徵風召雨',
'征验' => '徵驗',
-'德占' => '德佔',
'心愿' => '心愿',
'心于' => '心於',
'心理' => '心理',
@@ -5719,7 +5296,6 @@ $zh2Hant = array(
'心系英' => '心繫英',
'心系茶' => '心繫茶',
'心系万' => '心繫萬',
-'心系着' => '心繫著',
'心系兰' => '心繫蘭',
'心系西' => '心繫西',
'心系贫' => '心繫貧',
@@ -5738,26 +5314,24 @@ $zh2Hant = array(
'心系麦' => '心繫麥',
'心系黄' => '心繫黃',
'心脏' => '心臟',
+'心脏痳痹' => '心臟痲痺',
'心荡' => '心蕩',
-'心药' => '心藥',
'心里面' => '心裏面',
'心里' => '心裡',
'心长发短' => '心長髮短',
'心余' => '心餘',
'必须' => '必須',
-'忙并' => '忙併',
'忙里' => '忙裡',
'忙里偷闲' => '忙裡偷閒',
'忠人之托' => '忠人之托',
'忠仆' => '忠僕',
'忠于' => '忠於',
'快干' => '快乾',
-'快克制' => '快剋制',
'快快当当' => '快快當當',
'快冲' => '快衝',
-'怎么' => '怎麼',
-'怎么着' => '怎麼著',
'怒于' => '怒於',
+'怒气冲天' => '怒氣衝天',
+'怒火冲天' => '怒火衝天',
'怒发冲冠' => '怒髮衝冠',
'思如泉涌' => '思如泉湧',
'怠于' => '怠於',
@@ -5765,6 +5339,7 @@ $zh2Hant = array(
'急冲而下' => '急衝而下',
'性征' => '性徵',
'性欲' => '性慾',
+'怨气冲天' => '怨氣衝天',
'怪里怪气' => '怪裡怪氣',
'怫郁' => '怫鬱',
'恂栗' => '恂慄',
@@ -5772,35 +5347,42 @@ $zh2Hant = array(
'恕乏价催' => '恕乏价催',
'息交绝游' => '息交絕遊',
'息谷' => '息穀',
-'恰才' => '恰纔',
-'悍药' => '悍藥',
'悒郁' => '悒鬱',
'悠悠荡荡' => '悠悠蕩蕩',
'悠荡' => '悠蕩',
'悠游' => '悠遊',
-'您克制' => '您剋制',
+'悲凄' => '悲悽',
'悲筑' => '悲筑',
'悲郁' => '悲鬱',
-'闷着头儿干' => '悶著頭兒幹',
'悸栗' => '悸慄',
+'凄厉' => '悽厲',
+'凄怨' => '悽怨',
+'凄惋' => '悽惋',
+'凄惶' => '悽惶',
+'凄恻' => '悽惻',
+'凄怆' => '悽愴',
+'凄惨' => '悽慘',
+'凄戾' => '悽戾',
+'凄然' => '悽然',
+'凄美' => '悽美',
+'凄苦' => '悽苦',
+'凄酸' => '悽酸',
'情欲' => '情慾',
'惇朴' => '惇樸',
+'恶仆' => '惡僕',
'恶直丑正' => '惡直醜正',
'恶斗' => '惡鬥',
-'想克制' => '想剋制',
'惴栗' => '惴慄',
-'意占' => '意佔',
-'意克制' => '意剋制',
'意大利面' => '意大利麵',
-'意面' => '意麵',
'爱困' => '愛睏',
-'感冒药' => '感冒藥',
'感于' => '感於',
'愿朴' => '愿樸',
+'愿樸' => '愿樸',
'愿而恭' => '愿而恭',
'栗冽' => '慄冽',
'栗栗' => '慄慄',
'慌里慌张' => '慌裡慌張',
+'惨淡' => '慘澹',
'庆吊' => '慶弔',
'庆历' => '慶曆',
'庆历史' => '慶歷史',
@@ -5817,10 +5399,10 @@ $zh2Hant = array(
'凭折' => '憑摺',
'凭准' => '憑準',
'凭借' => '憑藉',
-'凭借着' => '憑藉著',
+'凭闲' => '憑閑',
+'宪法里' => '憲法裡',
'恳托' => '懇託',
'懈松' => '懈鬆',
-'应克制' => '應剋制',
'应征' => '應徵',
'应钟' => '應鐘',
'懔栗' => '懍慄',
@@ -5829,7 +5411,6 @@ $zh2Hant = array(
'蒙直' => '懞直',
'惩忿窒欲' => '懲忿窒欲',
'怀里' => '懷裡',
-'怀表' => '懷錶',
'怀钟' => '懷鐘',
'悬挂' => '懸掛',
'悬梁' => '懸樑',
@@ -5839,44 +5420,44 @@ $zh2Hant = array(
'恋恋不舍' => '戀戀不捨',
'成于' => '成於',
'成于思' => '成於思',
-'成药' => '成藥',
-'我克制' => '我剋制',
'戬谷' => '戩穀',
'截发' => '截髮',
'战天斗地' => '戰天鬥地',
'战栗' => '戰慄',
'战斗' => '戰鬥',
-'戏彩娱亲' => '戲綵娛親',
'戏里' => '戲裡',
+'戲院里' => '戲院里',
'戴表元' => '戴表元',
-'戴表' => '戴錶',
'戴发含齿' => '戴髮含齒',
'房里' => '房裡',
'所云' => '所云',
'所云云' => '所云云',
-'所占' => '所佔',
'所占卜' => '所占卜',
'所占星' => '所占星',
'所占算' => '所占算',
'所托' => '所託',
'扁拟谷盗虫' => '扁擬穀盜蟲',
-'手塚治虫' => '手塚治虫',
'手冢治虫' => '手塚治虫',
+'手塚治虫' => '手塚治虫',
'手折' => '手摺',
+'手表態' => '手表態',
'手表态' => '手表態',
'手表明' => '手表明',
+'手表決' => '手表決',
'手表决' => '手表決',
'手表演' => '手表演',
+'手表現' => '手表現',
'手表现' => '手表現',
'手表示' => '手表示',
+'手表達' => '手表達',
'手表达' => '手表達',
'手表露' => '手表露',
'手表面' => '手表面',
'手里剑' => '手裏劍',
'手里' => '手裡',
'手表' => '手錶',
+'手链' => '手鍊',
'手松' => '手鬆',
-'才克制' => '才剋制',
'才干休' => '才干休',
'才干戈' => '才干戈',
'才干扰' => '才干擾',
@@ -5890,25 +5471,24 @@ $zh2Hant = array(
'扑打' => '扑打',
'扑挞' => '扑撻',
'打干哕' => '打乾噦',
-'打并' => '打併',
'打出吊入' => '打出弔入',
'打卡钟' => '打卡鐘',
'打吨' => '打吨',
'打干' => '打幹',
'打拼' => '打拚',
'打断发' => '打斷發',
+'打卤' => '打滷',
'打谷' => '打穀',
-'打着钟' => '打著鐘',
-'打路庄板' => '打路莊板',
'打钟' => '打鐘',
'打风后' => '打風後',
'打斗' => '打鬥',
'托管国' => '托管國',
'扛大梁' => '扛大樑',
-'扞御' => '扞禦',
+'捍御' => '扞禦',
'扯面' => '扯麵',
-'扶余国' => '扶餘國',
+'扶余' => '扶餘',
'批准的' => '批准的',
+'批准确定' => '批准確定',
'批复' => '批覆',
'批注' => '批註',
'批斗' => '批鬥',
@@ -5917,15 +5497,11 @@ $zh2Hant = array(
'抑制剂' => '抑制劑',
'抑郁' => '抑鬱',
'抓奸' => '抓姦',
-'抓药' => '抓藥',
'抓斗' => '抓鬥',
-'投药' => '投藥',
-'抗癌药' => '抗癌藥',
'抗御' => '抗禦',
-'抗药' => '抗藥',
-'抗碱' => '抗鹼',
'折向往' => '折向往',
'折子戏' => '折子戲',
+'折子戲' => '折子戲',
'折戟沈河' => '折戟沈河',
'折冲' => '折衝',
'披榛采兰' => '披榛採蘭',
@@ -5942,7 +5518,6 @@ $zh2Hant = array(
'拆伙' => '拆夥',
'拈须' => '拈鬚',
'拉克施尔德钟' => '拉克施爾德鐘',
-'拉杆' => '拉杆',
'拉纤' => '拉縴',
'拉面上' => '拉面上',
'拉面具' => '拉面具',
@@ -5975,6 +5550,7 @@ $zh2Hant = array(
'括发' => '括髮',
'拭干' => '拭乾',
'拮据' => '拮据',
+'拳局' => '拳跼',
'拼死拼活' => '拼死拼活',
'拾沈' => '拾瀋',
'拿下表' => '拿下錶',
@@ -5987,21 +5563,19 @@ $zh2Hant = array(
'挂念' => '挂念',
'挂号' => '挂號',
'挂车' => '挂車',
-'挂面' => '挂面',
'挌斗' => '挌鬥',
'挑大梁' => '挑大樑',
'挑斗' => '挑鬥',
'振荡' => '振蕩',
-'捆扎' => '捆紮',
'捉奸徒' => '捉奸徒',
'捉奸细' => '捉奸細',
'捉奸贼' => '捉奸賊',
'捉奸党' => '捉奸黨',
'捉奸' => '捉姦',
'捉发' => '捉髮',
-'捍御' => '捍禦',
'捏面人' => '捏麵人',
'舍不得' => '捨不得',
+'舍入' => '捨入',
'舍出' => '捨出',
'舍去' => '捨去',
'舍命' => '捨命',
@@ -6031,39 +5605,44 @@ $zh2Hant = array(
'卷去' => '捲去',
'卷图' => '捲圖',
'卷土重来' => '捲土重來',
+'卷地' => '捲地',
'卷尺' => '捲尺',
+'卷尾猴' => '捲尾猴',
'卷心菜' => '捲心菜',
'卷成' => '捲成',
'卷曲' => '捲曲',
'卷款' => '捲款',
'卷毛' => '捲毛',
-'卷烟' => '捲煙',
+'卷烟盒' => '捲煙盒',
+'卷积云' => '捲積雲',
'卷筒' => '捲筒',
'卷帘' => '捲簾',
'卷纸' => '捲紙',
'卷缩' => '捲縮',
'卷舌' => '捲舌',
-'卷舖盖' => '捲舖蓋',
-'卷菸' => '捲菸',
+'卷铺盖' => '捲舖蓋',
+'卷烟' => '捲菸',
+'卷叶蛾' => '捲葉蛾',
'卷袖' => '捲袖',
'卷走' => '捲走',
'卷起' => '捲起',
'卷轴' => '捲軸',
'卷逃' => '捲逃',
-'卷铺盖' => '捲鋪蓋',
'卷云' => '捲雲',
'卷风' => '捲風',
-'卷发' => '捲髮',
+'卷发器' => '捲髮器',
'捵面' => '捵麵',
'捶炼' => '捶鍊',
'扫荡' => '掃蕩',
+'授勋' => '授勳',
'掌柜' => '掌柜',
'排骨面' => '排骨麵',
'挂名' => '掛名',
'挂帘' => '掛帘',
'挂历' => '掛曆',
-'挂鈎' => '掛鈎',
+'挂钩' => '掛鈎',
'挂钟' => '掛鐘',
+'挂面' => '掛麵',
'采下' => '採下',
'采伐' => '採伐',
'采住' => '採住',
@@ -6101,15 +5680,14 @@ $zh2Hant = array(
'采珠' => '採珠',
'采生折割' => '採生折割',
'采用' => '採用',
-'采的' => '採的',
'采石' => '採石',
'采砂场' => '採砂場',
'采矿' => '採礦',
'采种' => '採種',
'采空区' => '採空區',
'采空采穗' => '採空採穗',
-'采納' => '採納',
'采纳' => '採納',
+'采納' => '採納',
'采给' => '採給',
'采花' => '採花',
'采芹人' => '採芹人',
@@ -6138,7 +5716,6 @@ $zh2Hant = array(
'采食' => '採食',
'采盐' => '採鹽',
'掣签' => '掣籤',
-'接着说' => '接著說',
'控制' => '控制',
'推情准理' => '推情準理',
'推托之词' => '推托之詞',
@@ -6149,7 +5726,6 @@ $zh2Hant = array(
'提摩太后书' => '提摩太後書',
'插于' => '插於',
'换签' => '換籤',
-'换药' => '換藥',
'换只' => '換隻',
'换发' => '換髮',
'握发' => '握髮',
@@ -6158,20 +5734,17 @@ $zh2Hant = array(
'揪发' => '揪髮',
'揪须' => '揪鬚',
'揭丑' => '揭醜',
+'揮手表' => '揮手表',
'挥手表' => '揮手表',
-'挥杆' => '揮杆',
'搋面' => '搋麵',
'损于' => '損於',
'搏斗' => '搏鬥',
-'摇摇荡荡' => '搖搖蕩蕩',
-'摇荡' => '搖蕩',
'捣鬼吊白' => '搗鬼弔白',
-'搤肮拊背' => '搤肮拊背',
+'扼肮' => '搤肮',
+'扼肮拊背' => '搤肮拊背',
'搬斗' => '搬鬥',
'搭干铺' => '搭乾鋪',
'搭伙' => '搭夥',
-'抢占' => '搶佔',
-'搽药' => '搽藥',
'摧坚获丑' => '摧堅獲醜',
'摭采' => '摭採',
'摸棱' => '摸稜',
@@ -6183,6 +5756,7 @@ $zh2Hant = array(
'折扇' => '摺扇',
'折梯' => '摺梯',
'折椅' => '摺椅',
+'折台' => '摺檯',
'折叠' => '摺疊',
'折痕' => '摺痕',
'折篷' => '摺篷',
@@ -6192,7 +5766,6 @@ $zh2Hant = array(
'捞干' => '撈乾',
'捞面' => '撈麵',
'撚须' => '撚鬚',
-'撞球台' => '撞球檯',
'撞钟' => '撞鐘',
'撞阵冲军' => '撞陣衝軍',
'撤并' => '撤併',
@@ -6207,31 +5780,26 @@ $zh2Hant = array(
'操作钟' => '操作鐘',
'担仔面' => '擔仔麵',
'担担面' => '擔擔麵',
-'担着' => '擔著',
-'担负着' => '擔負著',
'据云' => '據云',
'据干而窥井底' => '據榦而窺井底',
'擢发' => '擢髮',
'擦干' => '擦乾',
'擦干净' => '擦乾淨',
-'擦药' => '擦藥',
'拧干' => '擰乾',
'摆钟' => '擺鐘',
'摄制' => '攝製',
'支干' => '支幹',
-'支杆' => '支杆',
+'支配欲' => '支配慾',
'收获' => '收穫',
'改征' => '改徵',
-'攻占' => '攻佔',
+'改采' => '改採',
'放蒙挣' => '放懞掙',
'放荡' => '放蕩',
'放松' => '放鬆',
-'故事里' => '故事裡',
+'政斗' => '政鬥',
'故云' => '故云',
'敏于' => '敏於',
-'救药' => '救藥',
'败于' => '敗於',
-'叙说着' => '敘說著',
'教学钟' => '教學鐘',
'教于' => '教於',
'教范' => '教範',
@@ -6245,37 +5813,52 @@ $zh2Hant = array(
'敬挽' => '敬輓',
'敲扑' => '敲扑',
'敲钟' => '敲鐘',
-'整庄' => '整莊',
'整只' => '整隻',
'整风后' => '整風後',
'整发用品' => '整髮用品',
+'整出剧' => '整齣劇',
+'整出戏' => '整齣戲',
+'整出电影' => '整齣電影',
'敌忾同仇' => '敵愾同讎',
-'敷药' => '敷藥',
+'数只包括' => '數只包括',
+'数只可' => '數只可',
+'数只含' => '數只含',
+'数只在' => '數只在',
+'数只应' => '數只應',
+'数只是' => '數只是',
+'数只会' => '數只會',
+'数只有' => '數只有',
+'数只比' => '數只比',
+'数只能' => '數只能',
+'数只限' => '數只限',
+'数只需' => '數只需',
+'数只须' => '數只須',
'数天后' => '數天後',
'数字钟' => '數字鐘',
'数字钟表' => '數字鐘錶',
-'数学家' => '數學家',
'数罪并罚' => '數罪併罰',
'数与虏确' => '數與虜确',
+'数只' => '數隻',
'文丑' => '文丑',
'文汇报' => '文匯報',
+'文学志' => '文學誌',
'文征明' => '文徵明',
'文思泉涌' => '文思泉湧',
'文采郁郁' => '文采郁郁',
-'斗转参横' => '斗轉參橫',
+'斗牛星' => '斗牛星',
'斫雕为朴' => '斫雕為樸',
'新井里美' => '新井里美',
'新历' => '新曆',
'新历史' => '新歷史',
'新扎' => '新紮',
-'新庄' => '新莊',
-'新庄市' => '新莊市',
'斲雕为朴' => '斲雕為樸',
'断发' => '斷髮',
'断发文身' => '斷髮文身',
'方便面' => '方便麵',
'方几' => '方几',
'方向往' => '方向往',
+'方志恒' => '方志恒',
+'方法里' => '方法裡',
'方志' => '方誌',
'方面' => '方面',
'于0' => '於0',
@@ -6307,7 +5890,6 @@ $zh2Hant = array(
'于你' => '於你',
'于八' => '於八',
'于六' => '於六',
-'于克制' => '於剋制',
'于前' => '於前',
'于劣' => '於劣',
'于勤' => '於勤',
@@ -6383,92 +5965,54 @@ $zh2Hant = array(
'于震中' => '於震中',
'于震前' => '於震前',
'于震后' => '於震后',
-'于0' => '於0',
-'于1' => '於1',
-'于2' => '於2',
-'于3' => '於3',
-'于4' => '於4',
-'于5' => '於5',
-'于6' => '於6',
-'于7' => '於7',
-'于8' => '於8',
-'于9' => '於9',
'施舍' => '施捨',
'施于' => '施於',
'施舍之道' => '施舍之道',
-'施药' => '施藥',
'旁征博引' => '旁徵博引',
'旁注' => '旁註',
'旅游' => '旅遊',
'旋干转坤' => '旋乾轉坤',
-'旋绕着' => '旋繞著',
'旋回' => '旋迴',
'族里' => '族裡',
-'旗杆' => '旗杆',
-'日占' => '日佔',
-'日子里' => '日子裡',
'日心历表' => '日心曆表',
-'日晒' => '日晒',
'日历' => '日曆',
'日历史' => '日歷史',
+'日里' => '日裡',
'日志' => '日誌',
'早于' => '早於',
'旱干' => '旱乾',
-'昆仑山' => '昆崙山',
+'升州' => '昇州',
'升平' => '昇平',
'升阳' => '昇陽',
'昊天不吊' => '昊天不弔',
-'明天' => '明天',
'明征' => '明徵',
'明目张胆' => '明目張胆',
'明窗净几' => '明窗淨几',
'明范' => '明範',
-'明里' => '明裡',
-'易克制' => '易剋制',
+'明鉴' => '明鑑',
'易于' => '易於',
-'星巴克' => '星巴克',
'星历' => '星曆',
'星期后' => '星期後',
'星历史' => '星歷史',
-'星辰表' => '星辰錶',
-'春假里' => '春假裡',
-'春天里' => '春天裡',
-'春日里' => '春日裡',
-'春药' => '春藥',
'春游' => '春遊',
'春香斗学' => '春香鬥學',
-'昨天' => '昨天',
+'是发小' => '是髮小',
'时钟' => '時鐘',
-'时间里' => '時間裡',
+'时间不准' => '時間不準',
'晃荡' => '晃蕩',
-'晋升' => '晉陞',
-'晒干' => '晒乾',
-'晒伤' => '晒傷',
-'晒图' => '晒圖',
-'晒图纸' => '晒圖紙',
-'晒成' => '晒成',
-'晒晒' => '晒晒',
-'晒烟' => '晒煙',
-'晒种' => '晒種',
-'晒衣' => '晒衣',
-'晒黑' => '晒黑',
'晚于' => '晚於',
'晚钟' => '晚鐘',
'晞发' => '晞髮',
'晨钟' => '晨鐘',
'普冬冬' => '普鼕鼕',
-'景致' => '景緻',
'晾干' => '晾乾',
-'晕船药' => '暈船藥',
-'晕车药' => '暈車藥',
-'暑假里' => '暑假裡',
'暗地里' => '暗地裡',
'暗沟里' => '暗溝裡',
'暗里' => '暗裡',
'暗斗' => '暗鬥',
'畅游' => '暢遊',
+'昵称' => '暱稱',
'暴敛横征' => '暴斂橫徵',
-'暴晒' => '暴晒',
'历元' => '曆元',
'历命' => '曆命',
'历始' => '曆始',
@@ -6482,24 +6026,22 @@ $zh2Hant = array(
'历狱' => '曆獄',
'历纪' => '曆紀',
'历象' => '曆象',
-'曝晒' => '曝晒',
+'晒干' => '曬乾',
'晒谷' => '曬穀',
'曰云' => '曰云',
'更仆难数' => '更僕難數',
+'更加注' => '更加注',
'更签' => '更籤',
'更钟' => '更鐘',
-'书呆子' => '書獃子',
'书签' => '書籤',
'书面' => '書面',
+'曹子里' => '曹子里',
'曼谷人' => '曼谷人',
'曾朴' => '曾樸',
'最多' => '最多',
-'会上签署' => '會上簽署',
-'会上签订' => '會上簽訂',
-'会占' => '會佔',
-'会占卜' => '會占卜',
-'会干扰' => '會干擾',
+'最多只' => '最多只',
'會干擾' => '會干擾',
+'会干扰' => '會干擾',
'会干' => '會幹',
'会吊' => '會弔',
'会里' => '會裡',
@@ -6514,10 +6056,11 @@ $zh2Hant = array(
'有只不' => '有只不',
'有只允' => '有只允',
'有只容' => '有只容',
-'有只採' => '有只採',
'有只采' => '有只採',
+'有只採' => '有只採',
'有只是' => '有只是',
'有只用' => '有只用',
+'有回复' => '有回覆',
'有够赞' => '有夠讚',
'有征伐' => '有征伐',
'有征战' => '有征戰',
@@ -6534,45 +6077,44 @@ $zh2Hant = array(
'有余' => '有餘',
'有发头陀寺' => '有髮頭陀寺',
'服于' => '服於',
-'服药' => '服藥',
'望了望' => '望了望',
'望后石' => '望后石',
-'望着表' => '望著錶',
-'望着钟' => '望著鐘',
-'望着钟表' => '望著鐘錶',
'朝乾夕惕' => '朝乾夕惕',
'朝钟' => '朝鐘',
'朦胧' => '朦朧',
'蒙胧' => '朦朧',
'木偶戏扎' => '木偶戲紮',
-'木杆' => '木杆',
'木材干馏' => '木材乾餾',
'木梁' => '木樑',
+'木签' => '木籤',
'木制' => '木製',
'木钟' => '木鐘',
'未干' => '未乾',
'未干涉' => '未干涉',
-'末药' => '末藥',
+'未干預' => '未干預',
+'未干预' => '未干預',
'本征' => '本徵',
+'本出戏' => '本齣戲',
'术赤' => '朮赤',
-'朱仑街' => '朱崙街',
'朱庆余' => '朱慶餘',
'朱理安历' => '朱理安曆',
'朱理安历史' => '朱理安歷史',
-'杆子' => '杆子',
+'朴子里' => '朴子里',
'李志喜' => '李志喜',
-'李連杰' => '李連杰',
'李连杰' => '李連杰',
+'李連杰' => '李連杰',
'材干' => '材幹',
-'村子里' => '村子裡',
-'村庄' => '村莊',
'村落发' => '村落發',
'村里' => '村裡',
-'村里长' => '村里長',
'村里長' => '村里長',
+'村里长' => '村里長',
'杜老志道' => '杜老誌道',
'杞宋无征' => '杞宋無徵',
'束发' => '束髮',
+'杠人' => '杠人',
+'杠梁' => '杠梁',
+'杠轂' => '杠轂',
+'杠毂' => '杠轂',
'杯干' => '杯乾',
'杯面' => '杯麵',
'杰伦' => '杰倫',
@@ -6581,20 +6123,21 @@ $zh2Hant = array(
'杰特' => '杰特',
'东周钟' => '東周鐘',
'东岳' => '東嶽',
+'東湖里' => '東湖里',
'东冲西突' => '東衝西突',
'东游' => '東遊',
'松山庄' => '松山庄',
-'板着脸' => '板著臉',
'板荡' => '板蕩',
'林宏岳' => '林宏嶽',
+'林杰樑' => '林杰樑',
'林郁方' => '林郁方',
'林钟' => '林鐘',
+'林鹅峰' => '林鵞峰',
'果干' => '果乾',
'果子干' => '果子乾',
-'枝不得大于干' => '枝不得大於榦',
+'果累累' => '果纍纍',
'枝干' => '枝幹',
'枯干' => '枯乾',
-'台历' => '枱曆',
'架钟' => '架鐘',
'某只' => '某隻',
'染指于' => '染指於',
@@ -6606,8 +6149,10 @@ $zh2Hant = array(
'柱梁' => '柱樑',
'柳诒征' => '柳詒徵',
'栖栖皇皇' => '栖栖皇皇',
+'栗栖溪' => '栗栖溪',
'校准' => '校準',
-'校仇' => '校讎',
+'校舍' => '校舍',
+'校仇学' => '校讎學',
'核准的' => '核准的',
'格于' => '格於',
'格范' => '格範',
@@ -6615,11 +6160,14 @@ $zh2Hant = array(
'格里高利历' => '格里高利曆',
'格斗' => '格鬥',
'桂圆干' => '桂圓乾',
-'桅杆' => '桅杆',
'桌几' => '桌几',
'桌历' => '桌曆',
'桌历史' => '桌歷史',
+'桌游' => '桌遊',
'桑干' => '桑乾',
+'杆枪' => '桿槍',
+'杆秤' => '桿秤',
+'杆菌' => '桿菌',
'梁上君子' => '梁上君子',
'条干' => '條幹',
'梨干' => '梨乾',
@@ -6627,30 +6175,28 @@ $zh2Hant = array(
'械系' => '械繫',
'械斗' => '械鬥',
'弃舍' => '棄捨',
+'棉里' => '棉裡',
'棉制' => '棉製',
'棒子面' => '棒子麵',
-'枣庄' => '棗莊',
'栋梁' => '棟樑',
'棫朴' => '棫樸',
'森林里' => '森林裡',
'棺材里' => '棺材裡',
'植发' => '植髮',
+'椒面' => '椒麵',
'椰枣干' => '椰棗乾',
'楊雅筑' => '楊雅筑',
'杨雅筑' => '楊雅筑',
-'楚庄问鼎' => '楚莊問鼎',
-'楚庄王' => '楚莊王',
-'楚庄绝缨' => '楚莊絕纓',
'桢干' => '楨幹',
'业余' => '業餘',
'榨干' => '榨乾',
+'枪杆' => '槍桿',
'杠杆' => '槓桿',
'乐器钟' => '樂器鐘',
'樊于期' => '樊於期',
'梁上' => '樑上',
'梁柱' => '樑柱',
'樗里子' => '樗里子',
-'标杆' => '標杆',
'标标致致' => '標標致致',
'标准' => '標準',
'标签' => '標籤',
@@ -6662,8 +6208,8 @@ $zh2Hant = array(
'模范14棒' => '模范14棒',
'模范21棒' => '模范21棒',
'模范七棒' => '模范七棒',
-'模范三军' => '模范三軍',
'模范三軍' => '模范三軍',
+'模范三军' => '模范三軍',
'模范棒棒堂' => '模范棒棒堂',
'模制' => '模製',
'样范' => '樣範',
@@ -6687,39 +6233,32 @@ $zh2Hant = array(
'朴陋' => '樸陋',
'朴马' => '樸馬',
'朴鲁' => '樸魯',
-'树干' => '樹榦',
+'树干' => '樹幹',
+'树林里' => '樹林裡',
'树梁' => '樹樑',
'桥梁' => '橋樑',
-'機械系' => '機械系',
'机械系' => '機械系',
+'機械系' => '機械系',
'机械表' => '機械錶',
'机械钟' => '機械鐘',
'机械钟表' => '機械鐘錶',
-'机绣' => '機繡',
'横征暴敛' => '橫徵暴斂',
-'横杆' => '橫杆',
'横梁' => '橫樑',
'横冲' => '橫衝',
-'台子' => '檯子',
'台布' => '檯布',
+'台历' => '檯曆',
'台灯' => '檯燈',
'台球' => '檯球',
-'台面' => '檯面',
+'台面上' => '檯面上',
'柜台' => '櫃檯',
'栉发工' => '櫛髮工',
-'栏杆' => '欄杆',
'欲海难填' => '欲海難填',
'欺蒙' => '欺矇',
'歌后' => '歌后',
'歌钟' => '歌鐘',
'欧游' => '歐遊',
-'止咳药' => '止咳藥',
'止于' => '止於',
-'止痛药' => '止痛藥',
-'止血药' => '止血藥',
-'正在叱咤' => '正在叱咤',
'正官庄' => '正官庄',
-'正当着' => '正當著',
'武丑' => '武丑',
'武后' => '武后',
'武斗' => '武鬥',
@@ -6737,7 +6276,7 @@ $zh2Hant = array(
'残余' => '殘餘',
'僵尸' => '殭屍',
'殷师牛斗' => '殷師牛鬥',
-'杀虫药' => '殺蟲藥',
+'殷鉴' => '殷鑑',
'壳里' => '殼裡',
'殿钟自鸣' => '殿鐘自鳴',
'毁于' => '毀於',
@@ -6748,7 +6287,6 @@ $zh2Hant = array(
'母丑' => '母醜',
'每每只' => '每每只',
'每只' => '每隻',
-'毒药' => '毒藥',
'毗婆尸佛' => '毗婆尸佛',
'毛坏' => '毛坏',
'毛姜' => '毛薑',
@@ -6762,24 +6300,26 @@ $zh2Hant = array(
'水准' => '水準',
'水无怜奈' => '水無怜奈',
'水里' => '水裡',
+'水里商工' => '水里商工',
'水里溪' => '水里溪',
-'水里浊水溪' => '水里濁水溪',
+'水里濁水溪' => '水里濁水溪',
'水里鄉' => '水里鄉',
-'水里乡' => '水里鄉',
-'水碱' => '水鹼',
+'水里高級商工' => '水里高級商工',
+'水里鳳林' => '水里鳳林',
+'水表' => '水錶',
'永历' => '永曆',
'永历史' => '永歷史',
'永志不忘' => '永誌不忘',
'求知欲' => '求知慾',
'求签' => '求籤',
'求道于盲' => '求道於盲',
-'汗碱' => '汗鹼',
+'污蔑' => '汙衊',
'池里' => '池裡',
-'污蔑' => '污衊',
+'汤卤' => '汤滷',
'汲于' => '汲於',
'决斗' => '決鬥',
+'沈海蓉' => '沈海蓉',
'沈淀' => '沈澱',
-'沈着' => '沈著',
'沈郁' => '沈鬱',
'沉淀' => '沉澱',
'沉郁' => '沉鬱',
@@ -6790,18 +6330,20 @@ $zh2Hant = array(
'没梢干' => '沒梢幹',
'没样范' => '沒樣範',
'没准' => '沒準',
-'没药' => '沒藥',
'冲冠发怒' => '沖冠髮怒',
+'冲天' => '沖天',
+'沙羡' => '沙羡',
'沙里淘金' => '沙裡淘金',
'河岳' => '河嶽',
'河流汇集' => '河流匯集',
'河里' => '河裡',
+'油泼面' => '油潑麵',
'油斗' => '油鬥',
'油面' => '油麵',
'治愈' => '治癒',
'沿溯' => '沿泝',
-'法占' => '法佔',
'法自制' => '法自制',
+'法里,' => '法裡,',
'泛游' => '泛遊',
'泡制' => '泡製',
'泡面' => '泡麵',
@@ -6814,6 +6356,7 @@ $zh2Hant = array(
'泱郁' => '泱鬱',
'泳气钟' => '泳氣鐘',
'洄游' => '洄遊',
+'洋河大曲' => '洋河大麴',
'洒家' => '洒家',
'洒扫' => '洒掃',
'洒水' => '洒水',
@@ -6827,12 +6370,13 @@ $zh2Hant = array(
'洗练' => '洗鍊',
'洗发' => '洗髮',
'洛钟东应' => '洛鐘東應',
+'洞里' => '洞裡',
'泄欲' => '洩慾',
'洪范' => '洪範',
+'洪谷子' => '洪谷子',
'洪适' => '洪适',
'洪钟' => '洪鐘',
'汹涌' => '洶湧',
-'派团参加' => '派團參加',
'流征' => '流徵',
'流于' => '流於',
'流荡' => '流蕩',
@@ -6840,29 +6384,27 @@ $zh2Hant = array(
'流风余韵' => '流風餘韻',
'浩浩荡荡' => '浩浩蕩蕩',
'浩荡' => '浩蕩',
-'浪琴表' => '浪琴錶',
'浪荡' => '浪蕩',
'浪游' => '浪遊',
'浮于' => '浮於',
'浮荡' => '浮蕩',
'浮夸' => '浮誇',
'浮松' => '浮鬆',
-'海上布雷' => '海上佈雷',
'海干' => '海乾',
-'海淀山后' => '海淀山後',
'海淀山後' => '海淀山後',
-'海湾布雷' => '海灣佈雷',
+'海淀山后' => '海淀山後',
+'浸卤' => '浸滷',
'涂善妮' => '涂善妮',
'涂坤' => '涂坤',
-'涂壯勳' => '涂壯勳',
'涂壮勋' => '涂壯勳',
+'涂壯勳' => '涂壯勳',
'涂天相' => '涂天相',
'涂姓' => '涂姓',
'涂序瑄' => '涂序瑄',
-'涂敏恒' => '涂敏恆',
'涂敏恆' => '涂敏恆',
-'涂澤民' => '涂澤民',
+'涂敏恒' => '涂敏恆',
'涂泽民' => '涂澤民',
+'涂澤民' => '涂澤民',
'涂绍煃' => '涂紹煃',
'涂羽卿' => '涂羽卿',
'涂謹申' => '涂謹申',
@@ -6873,11 +6415,9 @@ $zh2Hant = array(
'涂长望' => '涂長望',
'涂鸿钦' => '涂鴻欽',
'涂鴻欽' => '涂鴻欽',
-'消炎药' => '消炎藥',
-'消肿药' => '消腫藥',
-'液晶表' => '液晶錶',
'涳蒙' => '涳濛',
'涸干' => '涸乾',
+'凉席' => '涼蓆',
'凉面' => '涼麵',
'淋余土' => '淋餘土',
'淑范' => '淑範',
@@ -6892,25 +6432,22 @@ $zh2Hant = array(
'淫荡' => '淫蕩',
'淬炼' => '淬鍊',
'深山何处钟' => '深山何處鐘',
-'深渊里' => '深淵裡',
+'深山里' => '深山裡',
'淳于' => '淳于',
'淳朴' => '淳樸',
'渊淳岳峙' => '淵淳嶽峙',
+'渊里' => '淵裡',
'浅淀' => '淺澱',
'清心寡欲' => '清心寡欲',
-'清汤挂面' => '清湯掛麵',
-'减肥药' => '減肥藥',
'渠冲' => '渠衝',
-'测试' => '測試',
+'测不准' => '測不準',
'港制' => '港製',
+'游牧民族' => '游牧民族',
'游离' => '游離',
'浑朴' => '渾樸',
'浑个' => '渾箇',
-'凑合着' => '湊合著',
'湖里' => '湖裡',
-'湘绣' => '湘繡',
'湘累' => '湘纍',
-'湟潦生苹' => '湟潦生苹',
'涌上' => '湧上',
'涌来' => '湧來',
'涌入' => '湧入',
@@ -6923,7 +6460,6 @@ $zh2Hant = array(
'湮郁' => '湮鬱',
'汤下面' => '湯下麵',
'汤团' => '湯糰',
-'汤药' => '湯藥',
'汤面' => '湯麵',
'源于' => '源於',
'准不准' => '準不準',
@@ -6966,16 +6502,37 @@ $zh2Hant = array(
'滃郁' => '滃鬱',
'滑借' => '滑藉',
'汇丰' => '滙豐',
-'滷制' => '滷製',
-'滷面' => '滷麵',
+'卤了' => '滷了',
+'卤五花' => '滷五花',
+'卤味' => '滷味',
+'卤好' => '滷好',
+'卤子' => '滷子',
+'卤料' => '滷料',
+'卤水' => '滷水',
+'卤汁' => '滷汁',
+'卤湖' => '滷湖',
+'卤煮' => '滷煮',
+'卤牛' => '滷牛',
+'卤的' => '滷的',
+'卤肉' => '滷肉',
+'卤菜' => '滷菜',
+'卤蛋' => '滷蛋',
+'卤虾' => '滷蝦',
+'卤制' => '滷製',
+'卤豆' => '滷豆',
+'卤鸡' => '滷雞',
+'卤鸭' => '滷鴨',
+'卤鹅' => '滷鵝',
+'卤面' => '滷麵',
'满拼自尽' => '滿拚自盡',
'满满当当' => '滿滿當當',
'满头洋发' => '滿頭洋髮',
'漂荡' => '漂蕩',
'漕挽' => '漕輓',
'沤郁' => '漚鬱',
+'漠里' => '漠裡',
'汉弥登钟' => '漢彌登鐘',
-'汉弥登钟表公司' => '漢彌登鐘錶公司',
+'漫卷' => '漫捲',
'漫游' => '漫遊',
'潜意识里' => '潛意識裡',
'潜水表' => '潛水錶',
@@ -6998,7 +6555,7 @@ $zh2Hant = array(
'淀谓之滓' => '澱謂之滓',
'澹台' => '澹臺',
'澹荡' => '澹蕩',
-'激荡' => '激蕩',
+'激斗' => '激鬥',
'浓发' => '濃髮',
'蒙汜' => '濛汜',
'蒙蒙细雨' => '濛濛細雨',
@@ -7006,77 +6563,64 @@ $zh2Hant = array(
'蒙松雨' => '濛鬆雨',
'蒙鸿' => '濛鴻',
'滨田里佳子' => '濱田里佳子',
-'泻药' => '瀉藥',
'沈吉线' => '瀋吉線',
'沈山线' => '瀋山線',
'沈州' => '瀋州',
+'沈抚' => '瀋撫',
'沈水' => '瀋水',
'沈河' => '瀋河',
'沈海' => '瀋海',
'沈海铁路' => '瀋海鐵路',
'沈阳' => '瀋陽',
+'泸州大曲' => '瀘州大麯',
'潇洒' => '瀟洒',
'弥山遍野' => '瀰山遍野',
'弥漫' => '瀰漫',
-'弥漫着' => '瀰漫著',
'弥弥' => '瀰瀰',
-'灌药' => '灌藥',
'漓水' => '灕水',
'漓江' => '灕江',
'漓湘' => '灕湘',
'漓然' => '灕然',
'滩涂' => '灘涂',
+'滩席' => '灘蓆',
'火并非' => '火並非',
'火并' => '火併',
+'火山里' => '火山裡',
'火拼' => '火拚',
'火折子' => '火摺子',
-'火箭布雷' => '火箭佈雷',
'火签' => '火籤',
-'火药' => '火藥',
'灰蒙' => '灰濛',
'灰蒙蒙' => '灰濛濛',
'炆面' => '炆麵',
'炒面' => '炒麵',
'炮制' => '炮製',
-'炸药' => '炸藥',
'炸酱面' => '炸醬麵',
'为准' => '為準',
-'为着' => '為著',
+'为鉴' => '為鑑',
+'乌兹冲锋枪' => '烏茲衝鋒槍',
'乌发' => '烏髮',
'乌龙面' => '烏龍麵',
'烘干' => '烘乾',
'烘制' => '烘製',
'烤干' => '烤乾',
-'烤晒' => '烤晒',
+'烤卤' => '烤滷',
'焙干' => '焙乾',
'无征不信' => '無徵不信',
'无业游民' => '無業游民',
'无梁楼盖' => '無樑樓蓋',
-'无法克制' => '無法剋制',
-'无药可救' => '無藥可救',
'無言不仇' => '無言不讎',
'无余' => '無餘',
-'然身死才数月耳' => '然身死纔數月耳',
-'炼药' => '煉藥',
'炼制' => '煉製',
-'煎药' => '煎藥',
'煎面' => '煎麵',
'烟卷' => '煙捲',
-'烟斗丝' => '煙斗絲',
-'烟碱' => '煙鹼',
-'照占' => '照佔',
'照入签' => '照入籤',
-'照准' => '照準',
'照相干片' => '照相乾片',
'煨干' => '煨乾',
'煮面' => '煮麵',
'熊杰' => '熊杰',
'荧郁' => '熒鬱',
-'熬药' => '熬藥',
-'燉药' => '燉藥',
'燎发' => '燎髮',
'烧干' => '燒乾',
-'烧碱' => '燒鹼',
'燕几' => '燕几',
'燕巢于幕' => '燕巢於幕',
'燕燕于飞' => '燕燕于飛',
@@ -7089,6 +6633,7 @@ $zh2Hant = array(
'烫发' => '燙髮',
'烫面' => '燙麵',
'营干' => '營幹',
+'烩面' => '燴麵',
'烬余' => '燼餘',
'爆发指数' => '爆發指數',
'争奇斗妍' => '爭奇鬥妍',
@@ -7100,26 +6645,25 @@ $zh2Hant = array(
'争斗' => '爭鬥',
'爰定祥历' => '爰定祥厤',
'爽荡' => '爽蕩',
-'尔冬升' => '爾冬陞',
+'尔冬陞' => '爾冬陞',
'墙里' => '牆裡',
'片言只语' => '片言隻語',
+'版图里' => '版圖裡',
'牙签' => '牙籤',
-'牛肉面' => '牛肉麵',
'牛只' => '牛隻',
'物欲' => '物慾',
-'物理学家' => '物理學家',
+'抵牾' => '牴牾',
+'抵触' => '牴觸',
'特别致' => '特别致',
'特制住' => '特制住',
'特制定' => '特制定',
'特制止' => '特制止',
'特制订' => '特制訂',
'特征' => '特徵',
-'特效药' => '特效藥',
'特制' => '特製',
'牵一发' => '牽一髮',
'牵系' => '牽繫',
'荦确' => '犖确',
-'狂占' => '狂佔',
'狂并潮' => '狂併潮',
'狃于' => '狃於',
'狄志杰' => '狄志杰',
@@ -7131,43 +6675,25 @@ $zh2Hant = array(
'犹如钟' => '猶如鐘',
'犹如钟表' => '猶如鐘錶',
'呆串了皮' => '獃串了皮',
-'呆事' => '獃事',
-'呆人' => '獃人',
-'呆子' => '獃子',
-'呆性' => '獃性',
-'呆想' => '獃想',
-'呆憨呆' => '獃憨獃',
-'呆根' => '獃根',
-'呆气' => '獃氣',
-'呆滞' => '獃滯',
-'呆呆' => '獃獃',
-'呆痴' => '獃痴',
-'呆磕' => '獃磕',
-'呆等' => '獃等',
-'呆脑' => '獃腦',
-'呆着' => '獃著',
-'呆话' => '獃話',
-'呆头' => '獃頭',
'狱里' => '獄裡',
'奖杯' => '獎盃',
-'独占' => '獨佔',
-'独占鳌头' => '獨佔鰲頭',
+'独裁制' => '獨裁制',
'独辟蹊径' => '獨闢蹊徑',
'获匪其丑' => '獲匪其醜',
'兽欲' => '獸慾',
'献丑' => '獻醜',
-'率团参加' => '率團參加',
'玉历' => '玉曆',
'玉历史' => '玉歷史',
+'玉米面' => '玉米面',
'王侯后' => '王侯后',
'王后' => '王后',
'王田里' => '王田里',
-'王庄' => '王莊',
+'王鉴' => '王鑑',
'王余鱼' => '王餘魚',
'珍肴异馔' => '珍肴異饌',
'班里' => '班裡',
'现于' => '現於',
-'球杆' => '球杆',
+'球台' => '球檯',
'理一个发' => '理一個髮',
'理一次发' => '理一次髮',
'理个发' => '理個髮',
@@ -7175,6 +6701,7 @@ $zh2Hant = array(
'理次发' => '理次髮',
'理发' => '理髮',
'琴钟' => '琴鐘',
+'瑞城里' => '瑞城里',
'瑞征' => '瑞徵',
'瑶签' => '瑤籤',
'环游' => '環遊',
@@ -7182,13 +6709,11 @@ $zh2Hant = array(
'甄后' => '甄后',
'瓮安' => '甕安',
'甚于' => '甚於',
-'甚么' => '甚麼',
'甜水面' => '甜水麵',
'甜面酱' => '甜麵醬',
'生力面' => '生力麵',
'生于' => '生於',
'生殖洄游' => '生殖洄游',
-'生物学家' => '生物學家',
'生物钟' => '生物鐘',
'生发生' => '生發生',
'生华发' => '生華髮',
@@ -7196,29 +6721,29 @@ $zh2Hant = array(
'生锈' => '生鏽',
'生发' => '生髮',
'产卵洄游' => '產卵洄游',
-'用药' => '用藥',
+'苏醒' => '甦醒',
+'用法里' => '用法裡',
'甩发' => '甩髮',
+'田子里' => '田子里',
'田庄英雄' => '田庄英雄',
'田谷' => '田穀',
-'田庄' => '田莊',
'田里' => '田裡',
'由余' => '由余',
'由于' => '由於',
-'由表及里' => '由表及裡',
+'甲胄' => '甲冑',
'甲后路' => '甲后路',
-'男佣人' => '男佣人',
+'电影后' => '电影後',
'男仆' => '男僕',
-'男用表' => '男用錶',
+'界里' => '界裡',
'畏于' => '畏於',
'留发' => '留髮',
'毕于' => '畢於',
'毕业于' => '畢業於',
'毕生发展' => '畢生發展',
-'画着' => '畫著',
-'当家才知柴米价' => '當家纔知柴米價',
'当准' => '當準',
'当当丁丁' => '當當丁丁',
-'当着' => '當著',
+'当当网' => '當當網',
+'叠席' => '疊蓆',
'疏松' => '疏鬆',
'疑系' => '疑係',
'疑凶' => '疑兇',
@@ -7227,29 +6752,25 @@ $zh2Hant = array(
'病征' => '病徵',
'病愈' => '病癒',
'病余' => '病餘',
-'症候群' => '症候群',
'痊愈' => '痊癒',
'痒疹' => '痒疹',
'痒痒' => '痒痒',
-'痕迹' => '痕迹',
+'痳木' => '痳木',
+'痳疹' => '痳疹',
+'痳病' => '痳病',
+'痳痹' => '痳痺',
+'痳疯' => '痳瘋',
'愈合' => '癒合',
-'症候' => '癥候',
-'症状' => '癥狀',
'症结' => '癥結',
'癸丑' => '癸丑',
'发干' => '發乾',
-'发汗药' => '發汗藥',
'发呆' => '發獃',
'发蒙' => '發矇',
'发签' => '發籤',
-'发庄' => '發莊',
-'发着' => '發著',
-'发表' => '發表',
-'發表' => '發表',
'发松' => '發鬆',
'发面' => '發麵',
'白干' => '白乾',
-'白兔𢭏药' => '白兔擣藥',
+'白子里' => '白子里',
'白干儿' => '白干兒',
'白术' => '白朮',
'白朴' => '白樸',
@@ -7258,6 +6779,7 @@ $zh2Hant = array(
'白皮松' => '白皮松',
'白粉面' => '白粉麵',
'白里透红' => '白裡透紅',
+'白面包青天' => '白面包青天',
'白发' => '白髮',
'白胡' => '白鬍',
'白霉' => '白黴',
@@ -7266,10 +6788,9 @@ $zh2Hant = array(
'百只夠' => '百只夠',
'百只够' => '百只夠',
'百只怕' => '百只怕',
-'百只足够' => '百只足夠',
'百只足夠' => '百只足夠',
+'百只足够' => '百只足夠',
'百周后' => '百周後',
-'百多只' => '百多隻',
'百天后' => '百天後',
'百年' => '百年',
'百拙千丑' => '百拙千醜',
@@ -7278,14 +6799,15 @@ $zh2Hant = array(
'百扎' => '百紮',
'百花历' => '百花曆',
'百花历史' => '百花歷史',
-'百药之长' => '百藥之長',
'百炼' => '百鍊',
'百只' => '百隻',
'百余' => '百餘',
-'的克制' => '的剋制',
+'的回复' => '的回覆',
+'的图里' => '的圖裡',
+'的山里' => '的山裡',
'的钟' => '的鐘',
-'的钟表' => '的鐘錶',
'的长发' => '的長髮',
+'的发小' => '的髮小',
'皆可作淀' => '皆可作澱',
'皆准' => '皆準',
'皇后' => '皇后',
@@ -7293,9 +6815,9 @@ $zh2Hant = array(
'皇极历' => '皇極曆',
'皇极历史' => '皇極歷史',
'皇历史' => '皇歷史',
-'皇庄' => '皇莊',
'皓发' => '皓髮',
'皮制服' => '皮制服',
+'皮托管' => '皮托管',
'皮肤' => '皮膚',
'皮里春秋' => '皮裡春秋',
'皮里阳秋' => '皮裡陽秋',
@@ -7310,13 +6832,12 @@ $zh2Hant = array(
'盛赞' => '盛讚',
'盗采' => '盜採',
'盗钟' => '盜鐘',
-'尽量克制' => '盡量剋制',
'监制' => '監製',
'盘里' => '盤裡',
'盘回' => '盤迴',
'卢棱伽' => '盧稜伽',
+'荡气回肠' => '盪氣迴腸',
'盲干' => '盲幹',
-'直接参与' => '直接參与',
'直于' => '直於',
'直冲' => '直衝',
'相并' => '相併',
@@ -7329,10 +6850,8 @@ $zh2Hant = array(
'相斗' => '相鬥',
'看下表' => '看下錶',
'看下钟' => '看下鐘',
+'看法里' => '看法裡',
'看准' => '看準',
-'看着表' => '看著錶',
-'看着钟' => '看著鐘',
-'看着钟表' => '看著鐘錶',
'看表面' => '看表面',
'看表' => '看錶',
'看钟' => '看鐘',
@@ -7342,19 +6861,16 @@ $zh2Hant = array(
'眼帘' => '眼帘',
'眼眶里' => '眼眶裡',
'眼睛里' => '眼睛裡',
-'眼药' => '眼藥',
'眼里' => '眼裡',
'困乏' => '睏乏',
+'困了' => '睏了',
'困倦' => '睏倦',
'困觉' => '睏覺',
-'睡着了' => '睡著了',
'睡游病' => '睡遊病',
'瞄准' => '瞄準',
'瞅下表' => '瞅下錶',
'瞅下钟' => '瞅下鐘',
-'瞧着表' => '瞧著錶',
-'瞧着钟' => '瞧著鐘',
-'瞧着钟表' => '瞧著鐘錶',
+'瞎蒙' => '瞎矇',
'了望' => '瞭望',
'了然' => '瞭然',
'了若指掌' => '瞭若指掌',
@@ -7365,28 +6881,24 @@ $zh2Hant = array(
'蒙瞍' => '矇瞍',
'蒙眬' => '矇矓',
'蒙聩' => '矇聵',
-'蒙着' => '矇著',
-'蒙着锅儿' => '矇著鍋兒',
'蒙头转' => '矇頭轉',
'蒙骗' => '矇騙',
'瞩托' => '矚託',
-'矜庄' => '矜莊',
+'矜夸' => '矜誇',
'短几' => '短几',
'短于' => '短於',
'短发' => '短髮',
'矮几' => '矮几',
'石几' => '石几',
-'石家庄' => '石家莊',
+'石杠' => '石杠',
'石梁' => '石樑',
-'石英表' => '石英錶',
'石英钟' => '石英鐘',
'石英钟表' => '石英鐘錶',
-'石莼' => '石蓴',
-'石钟乳' => '石鐘乳',
-'石碱' => '石鹼',
-'矽谷' => '矽谷',
+'石钟' => '石鐘',
+'石钟山' => '石鐘山',
'研制' => '研製',
'砰当' => '砰噹',
+'破鉴' => '破鑑',
'朱唇皓齿' => '硃唇皓齒',
'朱批' => '硃批',
'朱砂' => '硃砂',
@@ -7394,18 +6906,18 @@ $zh2Hant = array(
'朱红色' => '硃紅色',
'朱色' => '硃色',
'朱谕' => '硃諭',
-'硫化碱' => '硫化鹼',
'硬干' => '硬幹',
'确瘠' => '确瘠',
'碑志' => '碑誌',
'碰钟' => '碰鐘',
+'确系' => '確係',
'码表' => '碼錶',
'磁制' => '磁製',
+'磨蝎' => '磨蝎',
'磨制' => '磨製',
'磨炼' => '磨鍊',
'磬钟' => '磬鐘',
'硗确' => '磽确',
-'碍难照准' => '礙難照准',
'砻谷机' => '礱穀機',
'示范' => '示範',
'社里' => '社裡',
@@ -7415,44 +6927,44 @@ $zh2Hant = array(
'神游' => '神遊',
'神雕像' => '神雕像',
'神雕' => '神鵰',
-'票庄' => '票莊',
'祭吊' => '祭弔',
-'祭吊文' => '祭弔文',
'禁欲' => '禁慾',
'禁欲主义' => '禁欲主義',
-'禁药' => '禁藥',
'祸于' => '禍於',
'御侮' => '禦侮',
'御寇' => '禦寇',
'御寒' => '禦寒',
'御敌' => '禦敵',
'礼赞' => '禮讚',
-'禹余粮' => '禹餘糧',
'禾谷' => '禾穀',
'秃妃之发' => '禿妃之髮',
'秃发' => '禿髮',
+'秀发动' => '秀發動',
+'秀发展' => '秀發展',
+'秀发布' => '秀發布',
+'秀发村' => '秀發村',
+'秀发现' => '秀發現',
+'秀发生' => '秀發生',
+'秀发表' => '秀發表',
+'秀发起' => '秀發起',
'秀发' => '秀髮',
'私下里' => '私下裡',
'私欲' => '私慾',
'私斗' => '私鬥',
-'秋假里' => '秋假裡',
-'秋天里' => '秋天裡',
-'秋日里' => '秋日裡',
-'秋裤' => '秋褲',
'秋游' => '秋遊',
'秋阴入井干' => '秋陰入井幹',
'秋发' => '秋髮',
+'种丹妮' => '种丹妮',
'种师中' => '种師中',
'种师道' => '种師道',
'种放' => '种放',
-'科学家' => '科學家',
+'科尼亚克期' => '科尼亞克期',
'科斗' => '科斗',
'科范' => '科範',
'秒表明' => '秒表明',
'秒表示' => '秒表示',
-'秒表' => '秒錶',
'秒钟' => '秒鐘',
-'秦庄襄王' => '秦莊襄王',
+'秤杆' => '秤桿',
'移祸于' => '移禍於',
'稀松' => '稀鬆',
'棱台' => '稜台',
@@ -7485,6 +6997,7 @@ $zh2Hant = array(
'谷物' => '穀物',
'谷皮' => '穀皮',
'谷神' => '穀神',
+'谷禄' => '穀祿',
'谷谷' => '穀穀',
'谷米' => '穀米',
'谷粒' => '穀粒',
@@ -7499,15 +7012,12 @@ $zh2Hant = array(
'谷食' => '穀食',
'穆罕默德历' => '穆罕默德曆',
'穆罕默德历史' => '穆罕默德歷史',
-'积极参与' => '積极參与',
-'积极参加' => '積极參加',
'积淀' => '積澱',
'积谷' => '積穀',
+'积谷防饥' => '積穀防饑',
'积郁' => '積鬱',
-'稳占' => '穩佔',
+'稳健的台风' => '穩健的台風',
'稳扎' => '穩紮',
-'空中布雷' => '空中佈雷',
-'空投布雷' => '空投佈雷',
'空蒙' => '空濛',
'空荡' => '空蕩',
'空荡荡' => '空蕩蕩',
@@ -7515,39 +7025,52 @@ $zh2Hant = array(
'空钟' => '空鐘',
'空余' => '空餘',
'窒欲' => '窒慾',
-'窗台上' => '窗台上',
-'窗帘' => '窗帘',
'窗明几亮' => '窗明几亮',
'窗明几净' => '窗明几淨',
-'窗台' => '窗檯',
+'窗帘' => '窗簾',
'窝里' => '窩裡',
'穷于' => '窮於',
'穷追不舍' => '窮追不捨',
'穷发' => '窮髮',
'窃钟掩耳' => '竊鐘掩耳',
-'立后综' => '立后綜',
'立于' => '立於',
'立范' => '立範',
'站干岸儿' => '站乾岸兒',
'童仆' => '童僕',
-'端庄' => '端莊',
'竞斗' => '競鬥',
'竹几' => '竹几',
'竹林之游' => '竹林之遊',
'竹签' => '竹籤',
+'竹席' => '竹蓆',
'竹制' => '竹製',
'笑里藏刀' => '笑裡藏刀',
-'笨笨呆呆' => '笨笨呆呆',
+'第一出现' => '第一出現',
+'第一出現' => '第一出現',
+'第一出線' => '第一出線',
+'第一出线' => '第一出線',
+'第一出' => '第一齣',
+'第七出' => '第七齣',
+'第三出局' => '第三出局',
+'第三出' => '第三齣',
+'第九出' => '第九齣',
+'第二出线' => '第二出線',
+'第二出線' => '第二出線',
+'第二出' => '第二齣',
+'第五出局' => '第五出局',
+'第五出' => '第五齣',
+'第八出' => '第八齣',
+'第六出' => '第六齣',
'第四出局' => '第四出局',
+'第四出' => '第四齣',
+'笔杆' => '筆桿',
'笔秃墨干' => '筆禿墨乾',
-'笔试' => '筆試',
'等于' => '等於',
'笋干' => '筍乾',
'筑前' => '筑前',
'筑北' => '筑北',
'筑州' => '筑州',
-'筑後' => '筑後',
'筑后' => '筑後',
+'筑後' => '筑後',
'筑波' => '筑波',
'筑紫' => '筑紫',
'筑肥' => '筑肥',
@@ -7556,7 +7079,6 @@ $zh2Hant = array(
'筑陽' => '筑陽',
'筑阳' => '筑陽',
'答复' => '答覆',
-'答覆' => '答覆',
'筵几' => '筵几',
'个中原因' => '箇中原因',
'个中奥妙' => '箇中奧妙',
@@ -7575,11 +7097,11 @@ $zh2Hant = array(
'算历史' => '算歷史',
'算准' => '算準',
'算发' => '算髮',
-'管人吊脚儿事' => '管人弔腳兒事',
'管制法' => '管制法',
'管干' => '管幹',
'箱里' => '箱裡',
'节欲' => '節慾',
+'节目里' => '節目裡',
'节余' => '節餘',
'范例' => '範例',
'范围' => '範圍',
@@ -7593,10 +7115,9 @@ $zh2Hant = array(
'范金' => '範金',
'简并' => '簡併',
'简朴' => '簡樸',
-'简筑翎' => '簡筑翎',
'簡筑翎' => '簡筑翎',
+'简筑翎' => '簡筑翎',
'簸荡' => '簸蕩',
-'签着' => '簽著',
'签幐' => '籤幐',
'签押' => '籤押',
'签条' => '籤條',
@@ -7604,8 +7125,10 @@ $zh2Hant = array(
'吁天' => '籲天',
'吁求' => '籲求',
'吁请' => '籲請',
+'米沈' => '米瀋',
'米谷' => '米穀',
-'粉拳绣腿' => '粉拳繡腿',
+'米团' => '米糰',
+'米面' => '米麵',
'粉签子' => '粉籤子',
'粗制' => '粗製',
'精制伏' => '精制伏',
@@ -7624,29 +7147,25 @@ $zh2Hant = array(
'粪秽蔑面' => '糞穢衊面',
'团子' => '糰子',
'系列里' => '系列裡',
-'系着' => '系著',
'系里' => '系裡',
'纪历' => '紀曆',
'纪历史' => '紀歷史',
-'约占' => '約佔',
'红后假说' => '紅后假說',
'红绳系足' => '紅繩繫足',
-'红色长发' => '紅色長髮',
'红钟' => '紅鐘',
-'红霉素' => '紅霉素',
'红发' => '紅髮',
'纡回' => '紆迴',
'纡余' => '紆餘',
'纡郁' => '紆鬱',
'纳征' => '納徵',
'纯朴' => '純樸',
-'纯碱' => '純鹼',
'纸扎' => '紙紮',
+'素数里' => '素數裡',
'素朴' => '素樸',
'素发' => '素髮',
'素面' => '素麵',
-'索马里' => '索馬里',
'索馬里' => '索馬里',
+'索马里' => '索馬里',
'索面' => '索麵',
'紫姜' => '紫薑',
'扎上' => '紮上',
@@ -7674,61 +7193,39 @@ $zh2Hant = array(
'结伴同游' => '結伴同遊',
'结伙' => '結夥',
'结扎' => '結紮',
-'结彩' => '結綵',
'结余' => '結餘',
'结发' => '結髮',
-'绝对参照' => '絕對參照',
'绝于' => '絕於',
'绞干' => '絞乾',
'络腮胡' => '絡腮鬍',
'給我干脆' => '給我干脆',
'给我干脆' => '給我干脆',
'给于' => '給於',
-'丝来线去' => '絲來線去',
-'丝布' => '絲布',
'丝恩发怨' => '絲恩髮怨',
-'丝板' => '絲板',
-'丝瓜布' => '絲瓜布',
-'丝绒布' => '絲絨布',
-'丝线' => '絲線',
-'丝织厂' => '絲織廠',
-'丝虫' => '絲蟲',
'丝制' => '絲製',
'丝发' => '絲髮',
'绑扎' => '綁紮',
-'綑扎' => '綑紮',
-'经有云' => '經有云',
+'捆扎' => '綑紮',
'經有云' => '經有云',
+'经有云' => '經有云',
+'综合征' => '綜合徵',
'绿发' => '綠髮',
-'绸缎庄' => '綢緞莊',
'维系' => '維繫',
'绾发' => '綰髮',
+'纲鉴' => '綱鑑',
'网里' => '網裡',
'网志' => '網誌',
'网游' => '網遊',
-'彩带' => '綵帶',
-'彩排' => '綵排',
-'彩楼' => '綵樓',
-'彩牌楼' => '綵牌樓',
-'彩球' => '綵球',
-'彩绸' => '綵綢',
-'彩线' => '綵線',
-'彩船' => '綵船',
-'彩衣' => '綵衣',
'紧致' => '緊緻',
-'紧绷' => '緊繃',
-'紧绷绷' => '緊繃繃',
-'紧绷着' => '緊繃著',
'紧追不舍' => '緊追不捨',
'绪余' => '緒餘',
-'緝凶' => '緝兇',
+'线图里' => '線圖裡',
'缉凶' => '緝兇',
-'编余' => '編余',
'编制法' => '編制法',
'编采' => '編採',
'编码表' => '編碼表',
-'编制' => '編製',
'编钟' => '編鐘',
+'编余' => '編餘',
'编发' => '編髮',
'缓征' => '緩徵',
'缓冲' => '緩衝',
@@ -7743,36 +7240,25 @@ $zh2Hant = array(
'纵欲' => '縱慾',
'纤夫' => '縴夫',
'纤手' => '縴手',
+'纤绳' => '縴繩',
+'总数只' => '總數只',
+'总数里' => '總數裡',
'总裁制' => '總裁制',
'繁复' => '繁複',
'繁钟' => '繁鐘',
-'绷住' => '繃住',
-'绷子' => '繃子',
-'绷带' => '繃帶',
'绷扒吊拷' => '繃扒弔拷',
-'绷紧' => '繃緊',
-'绷脸' => '繃臉',
-'绷着' => '繃著',
-'绷着脸' => '繃著臉',
-'绷着脸儿' => '繃著臉兒',
-'绷开' => '繃開',
'穗帏飘井干' => '繐幃飄井幹',
'绕梁' => '繞樑',
-'绣像' => '繡像',
-'绣口' => '繡口',
-'绣得' => '繡得',
-'绣户' => '繡戶',
-'绣房' => '繡房',
-'绣毯' => '繡毯',
-'绣球' => '繡球',
-'绣的' => '繡的',
-'绣花' => '繡花',
-'绣衣' => '繡衣',
-'绣起' => '繡起',
-'绣阁' => '繡閣',
-'绣鞋' => '繡鞋',
'绘制' => '繪製',
-'系上' => '繫上',
+'系上。' => '繫上。',
+'系上了' => '繫上了',
+'系上安全' => '繫上安全',
+'系上红' => '繫上紅',
+'系上丝' => '繫上絲',
+'系上绳' => '繫上繩',
+'系上头' => '繫上頭',
+'系上黑' => '繫上黑',
+'系上,' => '繫上,',
'系世' => '繫世',
'系到' => '繫到',
'系囚' => '繫囚',
@@ -7782,68 +7268,71 @@ $zh2Hant = array(
'系恋' => '繫戀',
'系于' => '繫於',
'系于一发' => '繫於一髮',
+'系着' => '繫着',
'系结' => '繫結',
'系紧' => '繫緊',
'系绳' => '繫繩',
'系累' => '繫纍',
+'系船' => '繫船',
'系辞' => '繫辭',
+'系鞋带' => '繫鞋帶',
'系风捕影' => '繫風捕影',
+'继承制' => '繼承制',
'累囚' => '纍囚',
'累堆' => '纍堆',
'累瓦结绳' => '纍瓦結繩',
'累绁' => '纍紲',
'累臣' => '纍臣',
'缠斗' => '纏鬥',
-'才则' => '纔則',
-'才可容颜十五余' => '纔可容顏十五餘',
-'才得两年' => '纔得兩年',
-'才此' => '纔此',
'坛子' => '罈子',
'坛坛罐罐' => '罈罈罐罐',
'坛騞' => '罈騞',
'置于' => '置於',
'置言成范' => '置言成範',
-'骂着' => '罵著',
'罢于' => '罷於',
+'罗马历' => '羅馬曆',
+'罗马历代' => '羅馬歷代',
+'罗马历史' => '羅馬歷史',
'羁系' => '羈繫',
-'美占' => '美佔',
-'美仑' => '美崙',
+'美容美发' => '美容美髮',
'美于' => '美於',
'美制' => '美製',
'美丑' => '美醜',
-'美发' => '美髮',
+'美发学' => '美髮學',
+'美发师' => '美髮師',
+'美发店' => '美髮店',
+'美发业' => '美髮業',
+'美发沙龙' => '美髮沙龍',
+'美发馆' => '美髮館',
'群丑' => '群醜',
'羡余' => '羨餘',
-'义占' => '義佔',
'义仆' => '義僕',
-'义庄' => '義莊',
+'義联' => '義联',
+'翁子里' => '翁子里',
'翕辟' => '翕闢',
'翱游' => '翱遊',
'翻涌' => '翻湧',
-'翻云覆雨' => '翻雲覆雨',
'翻松' => '翻鬆',
+'老么' => '老么',
'老干' => '老乾',
'老仆' => '老僕',
'老干部' => '老幹部',
'老蒙' => '老懞',
'老于' => '老於',
'老爷钟' => '老爺鐘',
-'老庄' => '老莊',
'老姜' => '老薑',
'老板' => '老闆',
'老面皮' => '老面皮',
'考征' => '考徵',
-'考试' => '考試',
-'而克制' => '而剋制',
'耍斗' => '耍鬥',
-'耐碱' => '耐鹼',
-'耕佣' => '耕傭',
'耕获' => '耕穫',
'耳余' => '耳餘',
'耿于' => '耿於',
'聊斋志异' => '聊齋志異',
+'圣人历' => '聖人曆',
'圣后' => '聖后',
'聘雇' => '聘僱',
+'聚药雄蕊' => '聚葯雄蕊',
'闻风后' => '聞風後',
'联系' => '聯繫',
'听于' => '聽於',
@@ -7852,6 +7341,7 @@ $zh2Hant = array(
'肉丝面' => '肉絲麵',
'肉羹面' => '肉羹麵',
'肉松' => '肉鬆',
+'肉面' => '肉麵',
'肚里' => '肚裡',
'肝脏' => '肝臟',
'肝郁' => '肝鬱',
@@ -7859,9 +7349,8 @@ $zh2Hant = array(
'肥筑方言' => '肥筑方言',
'肴馔' => '肴饌',
'肺脏' => '肺臟',
-'胃药' => '胃藥',
+'胃脏' => '胃臟',
'胃里' => '胃裡',
-'背向着' => '背向著',
'背地里' => '背地裡',
'胎发' => '胎髮',
'胜肽' => '胜肽',
@@ -7870,7 +7359,7 @@ $zh2Hant = array(
'胡子昂' => '胡子昂',
'胡朴安' => '胡樸安',
'胡里胡涂' => '胡裡胡塗',
-'能克制' => '能剋制',
+'胰脏' => '胰臟',
'能干休' => '能干休',
'能干戈' => '能干戈',
'能干扰' => '能干擾',
@@ -7885,6 +7374,7 @@ $zh2Hant = array(
'脊梁' => '脊樑',
'脱谷机' => '脫穀機',
'脱发' => '脫髮',
+'脺脏' => '脺臟',
'脾脏' => '脾臟',
'腊之以为饵' => '腊之以為餌',
'腊味' => '腊味',
@@ -7893,16 +7383,20 @@ $zh2Hant = array(
'肾脏' => '腎臟',
'腐干' => '腐乾',
'腐余' => '腐餘',
+'腑脏' => '腑臟',
'腕表' => '腕錶',
-'脑子里' => '腦子裡',
'脑干' => '腦幹',
'腰里' => '腰裡',
'脚注' => '腳註',
'脚炼' => '腳鍊',
-'膏药' => '膏藥',
-'肤发' => '膚髮',
+'肠脏' => '腸臟',
'胶卷' => '膠捲',
'膨松' => '膨鬆',
+'膵脏' => '膵臟',
+'臊子面' => '臊子麵',
+'脏器' => '臟器',
+'脏胸' => '臟胸',
+'脏腑' => '臟腑',
'臣仆' => '臣僕',
'卧游' => '臥遊',
'臧谷亡羊' => '臧穀亡羊',
@@ -7924,26 +7418,30 @@ $zh2Hant = array(
'自制的能' => '自制的能',
'自制能力' => '自制能力',
'自于' => '自於',
+'自然数里' => '自然數裡',
'自制' => '自製',
'自觉自愿' => '自覺自愿',
+'自夸' => '自誇',
+'臭气冲天' => '臭氣衝天',
'至多' => '至多',
+'至多只' => '至多只',
'至于' => '至於',
'致于' => '致於',
+'台佟' => '臺佟',
+'台静农' => '臺靜農',
'臻于' => '臻於',
'舂谷' => '舂穀',
-'与克制' => '與剋制',
-'兴致' => '興緻',
+'舉手表' => '舉手表',
'举手表' => '舉手表',
-'举手表决' => '舉手表決',
-'旧庄' => '舊庄',
+'舊庄' => '舊庄',
'旧历' => '舊曆',
'旧历史' => '舊歷史',
-'旧药' => '舊藥',
'旧游' => '舊遊',
'旧表' => '舊錶',
'旧钟' => '舊鐘',
'旧钟表' => '舊鐘錶',
'舌干唇焦' => '舌乾唇焦',
+'舍入口' => '舍入口',
'舒卷' => '舒捲',
'舞后' => '舞后',
'航海历' => '航海曆',
@@ -7954,31 +7452,25 @@ $zh2Hant = array(
'船钟' => '船鐘',
'船只' => '船隻',
'舰只' => '艦隻',
-'良药' => '良藥',
'色欲' => '色慾',
-'艷后' => '艷后',
+'色长发' => '色長髮',
'艳后' => '艷后',
+'艷后' => '艷后',
'艸木丰丰' => '艸木丰丰',
-'芍药' => '芍藥',
'芒果干' => '芒果乾',
-'花拳绣腿' => '花拳繡腿',
'花卷' => '花捲',
'花盆里' => '花盆裡',
-'花庵词选' => '花菴詞選',
-'花药' => '花藥',
+'花菴词选' => '花菴詞選',
+'花药' => '花葯',
'花钟' => '花鐘',
'花马吊嘴' => '花馬弔嘴',
'花哄' => '花鬨',
'苑里' => '苑裡',
-'苛性碱' => '苛性鹼',
'若干' => '若干',
'苦干' => '苦幹',
-'苦药' => '苦藥',
'苦里' => '苦裡',
'苦斗' => '苦鬥',
'苎麻' => '苧麻',
-'英占' => '英佔',
-'苹萦' => '苹縈',
'茂都淀' => '茂都澱',
'范文同' => '范文同',
'范文正公' => '范文正公',
@@ -7990,57 +7482,34 @@ $zh2Hant = array(
'范文藤' => '范文藤',
'范文虎' => '范文虎',
'范登堡' => '范登堡',
-'范贤惠' => '范賢惠',
'范賢惠' => '范賢惠',
+'范贤惠' => '范賢惠',
+'茅于轼' => '茅于軾',
+'茅于軾' => '茅于軾',
'茶几' => '茶几',
-'茶庄' => '茶莊',
'茶余' => '茶餘',
'茶面' => '茶麵',
'草丛里' => '草叢裡',
'草荐' => '草荐',
-'草药' => '草藥',
+'草席' => '草蓆',
'荐居' => '荐居',
'荐臻' => '荐臻',
-'荐饑' => '荐饑',
+'荐饥' => '荐饑',
'荷花淀' => '荷花澱',
-'庄上' => '莊上',
-'庄主' => '莊主',
-'庄周' => '莊周',
-'庄员' => '莊員',
-'庄严' => '莊嚴',
-'庄园' => '莊園',
-'庄士顿道' => '莊士頓道',
-'庄子' => '莊子',
-'庄客' => '莊客',
-'庄家' => '莊家',
-'庄户' => '莊戶',
-'庄房' => '莊房',
-'庄敬' => '莊敬',
-'庄田' => '莊田',
-'庄稼' => '莊稼',
-'庄舄越吟' => '莊舄越吟',
'庄里' => '莊裡',
-'庄语' => '莊語',
-'庄农' => '莊農',
-'庄重' => '莊重',
-'庄院' => '莊院',
-'庄骚' => '莊騷',
'茎干' => '莖幹',
'莜面' => '莜麵',
'莽荡' => '莽蕩',
-'菌丝体' => '菌絲體',
'菜干' => '菜乾',
+'菜坛' => '菜罈',
'菜肴' => '菜肴',
'菠棱菜' => '菠稜菜',
'菠萝干' => '菠蘿乾',
'华严钟' => '華嚴鐘',
-'华发' => '華髮',
-'菸碱' => '菸鹼',
'萬一只' => '萬一只',
'万一只' => '萬一只',
'万个' => '萬個',
'万周后' => '萬周後',
-'万多只' => '萬多隻',
'万天后' => '萬天後',
'万年' => '萬年',
'万年历' => '萬年曆',
@@ -8056,27 +7525,21 @@ $zh2Hant = array(
'落发' => '落髮',
'叶叶琴' => '葉叶琴',
'叶叶琹' => '葉叶琹',
-'着儿' => '著兒',
-'着克制' => '著剋制',
-'着书立说' => '著書立說',
-'着色软体' => '著色軟體',
-'着重指出' => '著重指出',
-'着录' => '著錄',
-'着录规则' => '著錄規則',
-'葡占' => '葡佔',
'葡萄干' => '葡萄乾',
'董氏封发' => '董氏封髮',
'葫芦里卖甚么药' => '葫蘆裡賣甚麼藥',
-'蒙汗药' => '蒙汗藥',
-'蒙庄' => '蒙莊',
'蒙雾露' => '蒙霧露',
'蒜发' => '蒜髮',
+'蒲席' => '蒲蓆',
+'蒸干' => '蒸乾',
+'蒸制' => '蒸製',
'苍术' => '蒼朮',
'苍发' => '蒼髮',
'苍郁' => '蒼鬱',
'蓄发' => '蓄髮',
'蓄胡' => '蓄鬍',
'蓄须' => '蓄鬚',
+'席子' => '蓆子',
'蓊郁' => '蓊鬱',
'蓬蓬松松' => '蓬蓬鬆鬆',
'蓬发' => '蓬髮',
@@ -8084,12 +7547,13 @@ $zh2Hant = array(
'参绥' => '蔘綏',
'葱郁' => '蔥鬱',
'荞麦面' => '蕎麥麵',
+'芸薹' => '蕓薹',
'荡来荡去' => '蕩來蕩去',
'荡女' => '蕩女',
'荡妇' => '蕩婦',
'荡寇' => '蕩寇',
'荡平' => '蕩平',
-'荡气回肠' => '蕩氣迴腸',
+'荡气' => '蕩氣',
'荡涤' => '蕩滌',
'荡漾' => '蕩漾',
'荡然' => '蕩然',
@@ -8100,6 +7564,7 @@ $zh2Hant = array(
'萧参' => '蕭蔘',
'薄幸' => '薄倖',
'薄干' => '薄幹',
+'姜啤' => '薑啤',
'姜是老的辣' => '薑是老的辣',
'姜末' => '薑末',
'姜桂' => '薑桂',
@@ -8115,110 +7580,61 @@ $zh2Hant = array(
'姜饼' => '薑餅',
'姜黄' => '薑黃',
'薙发' => '薙髮',
-'薝蔔' => '薝蔔',
+'薝卜' => '薝蔔',
+'熏心' => '薰心',
+'熏染' => '薰染',
+'熏沐' => '薰沐',
+'熏习' => '薰習',
+'熏陶' => '薰陶',
+'熏风' => '薰風',
+'熏香' => '薰香',
'苧悴' => '薴悴',
'薴烯' => '薴烯',
'苧烯' => '薴烯',
'借以' => '藉以',
'借助' => '藉助',
+'借口' => '藉口',
'借寇兵' => '藉寇兵',
'借手' => '藉手',
+'借故' => '藉故',
'借机' => '藉機',
'借此' => '藉此',
'借由' => '藉由',
'借箸代筹' => '藉箸代籌',
-'借着' => '藉著',
+'借词' => '藉詞',
'借资' => '藉資',
'蓝淀' => '藍澱',
'藏于' => '藏於',
'藏历' => '藏曆',
'藏历史' => '藏歷史',
'藏蒙歌儿' => '藏矇歌兒',
+'藤席' => '藤蓆',
'藤制' => '藤製',
-'药丸' => '藥丸',
-'药典' => '藥典',
-'药到命除' => '藥到命除',
-'药到病除' => '藥到病除',
-'药剂' => '藥劑',
-'药力' => '藥力',
-'药包' => '藥包',
-'药名' => '藥名',
-'药味' => '藥味',
-'药品' => '藥品',
-'药商' => '藥商',
-'药单' => '藥單',
-'药婆' => '藥婆',
-'药学' => '藥學',
-'药害' => '藥害',
-'药专' => '藥專',
-'药局' => '藥局',
-'药师' => '藥師',
-'药店' => '藥店',
-'药厂' => '藥廠',
-'药引' => '藥引',
-'药性' => '藥性',
-'药房' => '藥房',
-'药效' => '藥效',
-'药方' => '藥方',
-'药材' => '藥材',
-'药棉' => '藥棉',
-'药检局' => '藥檢局',
-'药水' => '藥水',
-'药油' => '藥油',
-'药液' => '藥液',
-'药渣' => '藥渣',
-'药片' => '藥片',
-'药物' => '藥物',
-'药王' => '藥王',
-'药理' => '藥理',
-'药瓶' => '藥瓶',
-'药用' => '藥用',
-'药皂' => '藥皂',
-'药盒' => '藥盒',
-'药石' => '藥石',
-'药科' => '藥科',
-'药箱' => '藥箱',
'药签' => '藥籤',
-'药粉' => '藥粉',
-'药糖' => '藥糖',
-'药线' => '藥線',
-'药罐' => '藥罐',
-'药膏' => '藥膏',
-'药舖' => '藥舖',
-'药茶' => '藥茶',
-'药草' => '藥草',
-'药行' => '藥行',
-'药贩' => '藥販',
-'药费' => '藥費',
-'药酒' => '藥酒',
-'药医学系' => '藥醫學系',
-'药量' => '藥量',
-'药针' => '藥針',
-'药铺' => '藥鋪',
-'药头' => '藥頭',
-'药饵' => '藥餌',
'药面儿' => '藥麵兒',
'苏昆' => '蘇崑',
-'蕴含着' => '蘊含著',
-'蕴涵着' => '蘊涵著',
+'苏崑' => '蘇崑',
+'苹果' => '蘋果',
'苹果干' => '蘋果乾',
-'萝蔔干' => '蘿蔔乾',
+'萝卜' => '蘿蔔',
+'萝卜干' => '蘿蔔乾',
'虎须' => '虎鬚',
'虎斗' => '虎鬥',
+'虚夸' => '虛誇',
'号志' => '號誌',
'虫部' => '虫部',
'蚊动牛斗' => '蚊動牛鬥',
'蛇发女妖' => '蛇髮女妖',
-'蛔虫药' => '蛔蟲藥',
'蜂后' => '蜂后',
'蜂涌' => '蜂湧',
'蜂准' => '蜂準',
'蜜里调油' => '蜜裡調油',
'蜡月' => '蜡月',
'蜡祭' => '蜡祭',
+'蝎虎' => '蝎虎',
'蝎蝎螫螫' => '蝎蝎螫螫',
'蝎谮' => '蝎譖',
-'虮蝨相吊' => '蟣蝨相弔',
+'虮虱相吊' => '蟣蝨相弔',
'蛏干' => '蟶乾',
'蚁后' => '蟻后',
'蟻后' => '蟻后',
@@ -8229,8 +7645,6 @@ $zh2Hant = array(
'行事历' => '行事曆',
'行事历史' => '行事歷史',
'行凶' => '行兇',
-'行凶前' => '行兇前',
-'行凶後' => '行兇後',
'行于' => '行於',
'行百里者半于九十' => '行百里者半於九十',
'卫后庄公' => '衛後莊公',
@@ -8253,7 +7667,7 @@ $zh2Hant = array(
'冲堂' => '衝堂',
'冲坚陷阵' => '衝堅陷陣',
'冲压' => '衝壓',
-'冲天' => '衝天',
+'冲天炮' => '衝天炮',
'冲州撞府' => '衝州撞府',
'冲心' => '衝心',
'冲掉' => '衝掉',
@@ -8267,11 +7681,11 @@ $zh2Hant = array(
'冲激' => '衝激',
'冲然' => '衝然',
'冲盹' => '衝盹',
+'冲着' => '衝着',
'冲破' => '衝破',
'冲程' => '衝程',
'冲突' => '衝突',
'冲线' => '衝線',
-'冲着' => '衝著',
'冲要' => '衝要',
'冲起' => '衝起',
'冲车' => '衝車',
@@ -8279,38 +7693,38 @@ $zh2Hant = array(
'冲过' => '衝過',
'冲量' => '衝量',
'冲锋' => '衝鋒',
+'冲锋枪' => '衝鋒鎗',
'冲陷' => '衝陷',
'冲头阵' => '衝頭陣',
'冲风' => '衝風',
-'衣绣昼行' => '衣繡晝行',
-'表征' => '表徵',
-'表里' => '表裡',
-'表面' => '表面',
+'衡鉴' => '衡鑑',
'衷于' => '衷於',
+'袋杆' => '袋桿',
'袋里' => '袋裡',
'袋表' => '袋錶',
'袖里' => '袖裡',
+'被废后' => '被廢後',
+'被系上' => '被繫上',
'被里' => '被裡',
-'被复' => '被複',
-'被覆着' => '被覆著',
+'被夸' => '被誇',
'被发佯狂' => '被髮佯狂',
'被发入山' => '被髮入山',
'被发左衽' => '被髮左衽',
'被发缨冠' => '被髮纓冠',
'被发阳狂' => '被髮陽狂',
+'夹衣' => '袷衣',
+'夹裙' => '袷裙',
'裁并' => '裁併',
'裁制' => '裁製',
'里手' => '裏手',
+'里水镇' => '裏水鎮',
'里海' => '裏海',
'补于' => '補於',
-'补药' => '補藥',
-'补血药' => '補血藥',
'补注' => '補註',
'装折' => '裝摺',
'里勾外连' => '裡勾外連',
'里屋' => '裡屋',
'里层' => '裡層',
-'里布' => '裡布',
'里带' => '裡帶',
'里弦' => '裡弦',
'里应外合' => '裡應外合',
@@ -8337,22 +7751,17 @@ $zh2Hant = array(
'制成' => '製成',
'制法' => '製法',
'制浆' => '製漿',
-'制为' => '製為',
'制片' => '製片',
'制版' => '製版',
'制程' => '製程',
'制糖' => '製糖',
'制纸' => '製紙',
'制药' => '製藥',
-'制表' => '製表',
+'制衣' => '製衣',
'制造' => '製造',
'制革' => '製革',
'制鞋' => '製鞋',
'制盐' => '製鹽',
-'复仞年如' => '複仞年如',
-'复以百万' => '複以百萬',
-'复位' => '複位',
-'复信' => '複信',
'复元音' => '複元音',
'复函数' => '複函數',
'复分数' => '複分數',
@@ -8362,11 +7771,7 @@ $zh2Hant = array(
'复利' => '複利',
'复印' => '複印',
'复句' => '複句',
-'复合' => '複合',
-'复名' => '複名',
-'复员' => '複員',
'复壁' => '複壁',
-'复壮' => '複壯',
'复姓' => '複姓',
'复字键' => '複字鍵',
'复审' => '複審',
@@ -8374,19 +7779,14 @@ $zh2Hant = array(
'复对数' => '複對數',
'复平面' => '複平面',
'复式' => '複式',
-'复复' => '複復',
'复数' => '複數',
'复本' => '複本',
'复查' => '複查',
-'复核' => '複核',
-'复检' => '複檢',
'复次' => '複次',
'复比' => '複比',
'复决' => '複決',
'复流' => '複流',
'复测' => '複測',
-'复亩珍' => '複畝珍',
-'复发' => '複發',
'复目' => '複目',
'复眼' => '複眼',
'复种' => '複種',
@@ -8409,38 +7809,24 @@ $zh2Hant = array(
'复钱' => '複錢',
'复阅' => '複閱',
'复杂' => '複雜',
-'复电' => '複電',
'复音' => '複音',
'复韵' => '複韻',
'褒赞' => '褒讚',
'衬里' => '襯裡',
-'西占' => '西佔',
'西周钟' => '西周鐘',
+'西昆' => '西崑',
'西岳' => '西嶽',
-'西晒' => '西晒',
'西历' => '西曆',
'西历史' => '西歷史',
+'西湖里' => '西湖里',
'西米谷' => '西米谷',
-'西药' => '西藥',
'西西里' => '西西里',
'西谷米' => '西谷米',
'西游' => '西遊',
-'要占' => '要佔',
-'要克制' => '要剋制',
-'要占卜' => '要占卜',
'要自制' => '要自制',
'要冲' => '要衝',
-'要么' => '要麼',
-'覆亡' => '覆亡',
-'覆命' => '覆命',
-'覆巢之下无完卵' => '覆巢之下無完卵',
-'覆水难收' => '覆水難收',
-'覆没' => '覆沒',
-'覆着' => '覆著',
-'覆盖' => '覆蓋',
-'覆盖着' => '覆蓋著',
-'覆辙' => '覆轍',
-'覆雨翻云' => '覆雨翻雲',
+'复信' => '覆信',
+'复核' => '覆核',
'见于' => '見於',
'见棱见角' => '見稜見角',
'见素抱朴' => '見素抱樸',
@@ -8449,12 +7835,11 @@ $zh2Hant = array(
'視如寇仇' => '視如寇讎',
'视于' => '視於',
'观采' => '觀採',
+'角抵' => '角牴',
'角落发' => '角落發',
'角落里' => '角落裡',
'觚棱' => '觚稜',
'解雇' => '解僱',
-'解痛药' => '解痛藥',
-'解药' => '解藥',
'解铃仍须系铃人' => '解鈴仍須繫鈴人',
'解铃还须系铃人' => '解鈴還須繫鈴人',
'解发佯狂' => '解髮佯狂',
@@ -8470,7 +7855,7 @@ $zh2Hant = array(
'托交' => '託交',
'托人' => '託人',
'托付' => '託付',
-'托儿所' => '託兒所',
+'托儿' => '託兒',
'托古讽今' => '託古諷今',
'托名' => '託名',
'托命' => '託命',
@@ -8494,7 +7879,6 @@ $zh2Hant = array(
'托附' => '託附',
'许愿起经' => '許愿起經',
'许虬' => '許虬',
-'诉说着' => '訴說著',
'注上' => '註上',
'注册' => '註冊',
'注失' => '註失',
@@ -8509,17 +7893,18 @@ $zh2Hant = array(
'注译' => '註譯',
'注销' => '註銷',
'注:' => '註:',
+'证谏' => '証諫',
'评断发' => '評斷發',
'评注' => '評註',
+'评鉴' => '評鑑',
'词干' => '詞幹',
'词汇' => '詞彙',
'词余' => '詞餘',
'询于' => '詢於',
'询于刍荛' => '詢於芻蕘',
-'试药' => '試藥',
'试制' => '試製',
-'诗云' => '詩云',
'詩云' => '詩云',
+'诗云' => '詩云',
'诗赞' => '詩讚',
'诗钟' => '詩鐘',
'诗余' => '詩餘',
@@ -8528,9 +7913,44 @@ $zh2Hant = array(
'详征博引' => '詳徵博引',
'详注' => '詳註',
'诔赞' => '誄讚',
+'夸下海口' => '誇下海口',
+'夸了' => '誇了',
+'夸他' => '誇他',
+'夸你' => '誇你',
+'夸来夸去' => '誇來誇去',
+'夸别' => '誇別',
+'夸功' => '誇功',
+'夸胜道强' => '誇勝道強',
+'夸口' => '誇口',
+'夸嘴' => '誇嘴',
'夸多斗靡' => '誇多鬥靡',
+'夸大' => '誇大',
+'夸她' => '誇她',
+'夸官' => '誇官',
+'夸张' => '誇張',
+'夸强说会' => '誇強說會',
+'夸得' => '誇得',
+'夸成' => '誇成',
+'夸我' => '誇我',
+'夸才' => '誇才',
+'夸毗' => '誇毗',
+'夸海口' => '誇海口',
+'夸奖' => '誇獎',
+'夸示' => '誇示',
+'夸称' => '誇稱',
+'夸耀' => '誇耀',
+'夸能' => '誇能',
'夸能斗智' => '誇能鬥智',
+'夸诩' => '誇詡',
+'夸夸' => '誇誇',
+'夸夸其谈' => '誇誇其談',
+'夸诞' => '誇誕',
+'夸说' => '誇說',
'夸赞' => '誇讚',
+'夸起' => '誇起',
+'夸辩' => '誇辯',
+'夸过' => '誇過',
+'夸饰' => '誇飾',
'志哀' => '誌哀',
'志喜' => '誌喜',
'志庆' => '誌慶',
@@ -8539,12 +7959,14 @@ $zh2Hant = array(
'诱奸' => '誘姦',
'语云' => '語云',
'语汇' => '語彙',
-'语有云' => '語有云',
'語有云' => '語有云',
+'语有云' => '語有云',
+'语法里' => '語法裡',
+'语里' => '語裡',
'诚征' => '誠徵',
'诚朴' => '誠樸',
'诬蔑' => '誣衊',
-'说着' => '說著',
+'说不准' => '說不準',
'谁干的' => '誰幹的',
'课征' => '課徵',
'课余' => '課餘',
@@ -8553,19 +7975,25 @@ $zh2Hant = array(
'调表' => '調錶',
'调钟表' => '調鐘錶',
'谈征' => '談徵',
-'请参阅' => '請參閱',
'请君入瓮' => '請君入甕',
-'请求' => '請求',
'请托' => '請託',
'咨询' => '諮詢',
'诸余' => '諸餘',
'谋干' => '謀幹',
-'谢绝参观' => '謝絕參觀',
'谢华后' => '謝華后',
'谬采虚声' => '謬採虛聲',
'谬赞' => '謬讚',
'謷丑' => '謷醜',
+'謹愿' => '謹愿',
+'谨愿' => '謹愿',
'谨于心' => '謹於心',
+'哗噪' => '譁噪',
+'哗嚣' => '譁囂',
+'哗然' => '譁然',
+'哗众' => '譁眾',
+'哗笑' => '譁笑',
+'哗变' => '譁變',
+'噪诈' => '譟詐',
'警世钟' => '警世鐘',
'警报钟' => '警報鐘',
'警示钟' => '警示鐘',
@@ -8574,21 +8002,15 @@ $zh2Hant = array(
'护发' => '護髮',
'变征' => '變徵',
'变丑' => '變醜',
-'变脏' => '變髒',
-'变髒' => '變髒',
-'仇問' => '讎問',
-'仇夷' => '讎夷',
-'仇校' => '讎校',
'仇正' => '讎正',
'仇隙' => '讎隙',
'赞不绝口' => '讚不絕口',
'赞佩' => '讚佩',
'赞呗' => '讚唄',
-'赞叹不已' => '讚嘆不已',
+'赞叹' => '讚嘆',
'赞扬' => '讚揚',
'赞乐' => '讚樂',
'赞歌' => '讚歌',
-'赞叹' => '讚歎',
'赞美' => '讚美',
'赞羡' => '讚羨',
'赞许' => '讚許',
@@ -8597,45 +8019,43 @@ $zh2Hant = array(
'赞赏' => '讚賞',
'赞辞' => '讚辭',
'赞颂' => '讚頌',
+'谷子敬' => '谷子敬',
'豆干' => '豆乾',
'豆腐干' => '豆腐乾',
-'竖着' => '豎著',
'竖起脊梁' => '豎起脊梁',
'丰滨' => '豐濱',
'丰滨乡' => '豐濱鄉',
+'丰台' => '豐臺',
+'豔后' => '豔后',
'象征' => '象徵',
-'象征着' => '象徵著',
-'负债累累' => '負債纍纍',
'贪欲' => '貪慾',
'贵价' => '貴价',
+'貴子里' => '貴子里',
'贵干' => '貴幹',
'贵征' => '貴徵',
-'買凶' => '買兇',
'买凶' => '買兇',
'买断发' => '買斷發',
-'费占' => '費佔',
'贻范' => '貽範',
-'资金占用' => '資金占用',
-'贾后' => '賈后',
'賈后' => '賈后',
+'贾后' => '賈后',
+'赈饥' => '賑饑',
'赏赞' => '賞讚',
-'贤后' => '賢后',
'賢后' => '賢后',
+'贤后' => '賢后',
'卖断发' => '賣斷發',
-'卖呆' => '賣獃',
+'赋范' => '賦范',
+'賦范' => '賦范',
+'质数里' => '質數裡',
'质朴' => '質樸',
'赌后' => '賭后',
'赌台' => '賭檯',
'赌斗' => '賭鬥',
-'賸余' => '賸餘',
'购并' => '購併',
'购买欲' => '購買慾',
'赢余' => '贏餘',
'赤术' => '赤朮',
'赤绳系足' => '赤繩繫足',
-'赤霉素' => '赤霉素',
'走回路' => '走回路',
-'起复' => '起複',
'起哄' => '起鬨',
'超级杯' => '超級盃',
'赶制' => '趕製',
@@ -8643,16 +8063,19 @@ $zh2Hant = array(
'赵威后' => '趙威后',
'赵惠后' => '趙惠后',
'赵治勋' => '趙治勳',
-'赵庄' => '趙莊',
'趱干' => '趲幹',
'足于' => '足於',
'跌扑' => '跌扑',
-'跌荡' => '跌蕩',
+'路图里' => '路圖裡',
'路签' => '路籤',
'路面' => '路面',
'跳梁小丑' => '跳樑小丑',
'跳荡' => '跳蕩',
-'跳表' => '跳錶',
+'局蹐' => '跼蹐',
+'局躅' => '跼躅',
+'踡局' => '踡跼',
+'逾闲' => '踰閑',
+'蹒局' => '蹣跼',
'蹪于' => '蹪於',
'蹭棱子' => '蹭稜子',
'躁郁' => '躁鬱',
@@ -8662,29 +8085,29 @@ $zh2Hant = array(
'车库里' => '車庫裡',
'车站里' => '車站裡',
'车里' => '車裡',
+'车里雅宾斯克' => '車里雅賓斯克',
'轨范' => '軌範',
-'军队克制' => '軍隊剋制',
'轩辟' => '軒闢',
'较于' => '較於',
'挽曲' => '輓曲',
'挽歌' => '輓歌',
-'挽聯' => '輓聯',
'挽联' => '輓聯',
-'挽詞' => '輓詞',
'挽词' => '輓詞',
'挽诗' => '輓詩',
-'挽詩' => '輓詩',
+'挽车' => '輓車',
+'挽输' => '輓輸',
+'挽辞' => '輓辭',
'轻于' => '輕於',
'轻轻松松' => '輕輕鬆鬆',
'轻松' => '輕鬆',
'轮奸' => '輪姦',
'轮回' => '輪迴',
'转向往' => '轉向往',
-'转台' => '轉檯',
'转托' => '轉託',
'转斗千里' => '轉鬥千里',
'辛丑' => '辛丑',
'辟谷' => '辟穀',
+'辣面' => '辣麵',
'办公台' => '辦公檯',
'辞汇' => '辭彙',
'辫发' => '辮髮',
@@ -8693,11 +8116,8 @@ $zh2Hant = array(
'农历史' => '農歷史',
'农民历' => '農民曆',
'农民历史' => '農民歷史',
-'农庄' => '農莊',
-'农药' => '農藥',
'迂回' => '迂迴',
'近日無仇' => '近日無讎',
-'近日里' => '近日裡',
'返朴' => '返樸',
'迥然回异' => '迥然迴異',
'迫于' => '迫於',
@@ -8708,12 +8128,13 @@ $zh2Hant = array(
'回形夹' => '迴形夾',
'回文' => '迴文',
'回旋' => '迴旋',
-'回流' => '迴流',
'回环' => '迴環',
'回纹针' => '迴紋針',
'回绕' => '迴繞',
'回翔' => '迴翔',
'回肠' => '迴腸',
+'回肠荡气' => '迴腸盪氣',
+'回荡' => '迴蕩',
'回诵' => '迴誦',
'回路' => '迴路',
'回转' => '迴轉',
@@ -8723,14 +8144,10 @@ $zh2Hant = array(
'回音' => '迴音',
'回响' => '迴響',
'回风' => '迴風',
-'迷幻药' => '迷幻藥',
'迷于' => '迷於',
'迷蒙' => '迷濛',
-'迷药' => '迷藥',
-'迷魂药' => '迷魂藥',
'追凶' => '追兇',
'退伙' => '退夥',
-'退烧药' => '退燒藥',
'退藏于密' => '退藏於密',
'逆钟' => '逆鐘',
'逆钟向' => '逆鐘向',
@@ -8738,41 +8155,50 @@ $zh2Hant = array(
'逋发' => '逋髮',
'逍遥游' => '逍遙遊',
'透辟' => '透闢',
+'这出世' => '這出世',
+'这出乎' => '這出乎',
+'这出人' => '這出人',
+'这出版' => '這出版',
+'这出现' => '這出現',
+'这出生' => '這出生',
+'这出色' => '這出色',
+'这出身' => '這出身',
+'这出道' => '這出道',
'这只不' => '這只不',
+'这只不过' => '這只不過',
'这只允' => '這只允',
+'这只包括' => '這只包括',
'这只可' => '這只可',
'这只在' => '這只在',
'这只容' => '這只容',
+'这只应' => '這只應',
'这只采' => '這只採',
'这只是' => '這只是',
'这只会' => '這只會',
+'这只比' => '這只比',
'这只用' => '這只用',
'这只能' => '這只能',
+'这只限' => '這只限',
'这只需' => '這只需',
+'这只须' => '這只須',
'这伙人' => '這夥人',
'这里' => '這裡',
'这钟' => '這鐘',
'这只' => '這隻',
-'这么' => '這麼',
-'这么着' => '這麼著',
+'这出' => '這齣',
'通奸' => '通姦',
'通心面' => '通心麵',
'通于' => '通於',
'通历' => '通曆',
'通历史' => '通歷史',
-'通庄' => '通莊',
-'逞凶鬥狠' => '逞兇鬥狠',
+'通鉴' => '通鑑',
'逞凶斗狠' => '逞兇鬥狠',
'造钟' => '造鐘',
-'造钟表' => '造鐘錶',
-'造曲' => '造麯',
'连三并四' => '連三併四',
-'连占' => '連佔',
'连采' => '連採',
'连系' => '連繫',
-'连庄' => '連莊',
'周游世界' => '週遊世界',
-'进占' => '進佔',
+'进两出' => '進兩出',
'進制' => '進制',
'进制' => '進制',
'逼并' => '逼併',
@@ -8783,7 +8209,7 @@ $zh2Hant = array(
'游伴' => '遊伴',
'游侠' => '遊俠',
'游冶' => '遊冶',
-'游刃有余' => '遊刃有餘',
+'游刃' => '遊刃',
'游动' => '遊動',
'游园' => '遊園',
'游子' => '遊子',
@@ -8802,16 +8228,16 @@ $zh2Hant = array(
'游历' => '遊歷',
'游民' => '遊民',
'游河' => '遊河',
+'游牧' => '遊牧',
'游猎' => '遊獵',
'游玩' => '遊玩',
-'游荡' => '遊盪',
'游目骋怀' => '遊目騁懷',
'游程' => '遊程',
'游丝' => '遊絲',
'游兴' => '遊興',
'游船' => '遊船',
'游艇' => '遊艇',
-'游荡不归' => '遊蕩不歸',
+'游荡' => '遊蕩',
'游艺' => '遊藝',
'游行' => '遊行',
'游街' => '遊街',
@@ -8821,24 +8247,20 @@ $zh2Hant = array(
'游资' => '遊資',
'游走' => '遊走',
'游踪' => '遊蹤',
+'游轮' => '遊輪',
'游逛' => '遊逛',
'游错' => '遊錯',
-'游离份子' => '遊離份子',
-'游离票' => '遊離票',
'游骑兵' => '遊騎兵',
'游魂' => '遊魂',
'过于' => '過於',
-'过杆' => '過杆',
'过水面' => '過水麵',
'道范' => '道範',
'逊于' => '遜於',
'递回' => '遞迴',
-'远县才至' => '遠縣纔至',
'远游' => '遠遊',
'遨游' => '遨遊',
'遮丑' => '遮醜',
'迁于' => '遷於',
-'选手' => '選手',
'选手表明' => '選手表明',
'选手表决' => '選手表決',
'选手表现' => '選手表現',
@@ -8846,54 +8268,56 @@ $zh2Hant = array(
'选手表达' => '選手表達',
'遗传钟' => '遺傳鐘',
'遗范' => '遺範',
-'遗迹' => '遺迹',
+'遗迹' => '遺蹟',
'辽沈' => '遼瀋',
-'避孕药' => '避孕藥',
'邀天之幸' => '邀天之倖',
-'还占' => '還佔',
'还采' => '還採',
'还冲' => '還衝',
'邋里邋遢' => '邋裡邋遢',
+'那只不过' => '那只不過',
+'那只包括' => '那只包括',
'那只可' => '那只可',
'那只在' => '那只在',
+'那只怕' => '那只怕',
+'那只应' => '那只應',
'那只是' => '那只是',
'那只会' => '那只會',
'那只有' => '那只有',
+'那只比' => '那只比',
'那只用' => '那只用',
'那只能' => '那只能',
+'那只限' => '那只限',
'那只需' => '那只需',
+'那只须' => '那只須',
'那卷' => '那捲',
'那里' => '那裡',
'那只' => '那隻',
-'那么' => '那麼',
-'那么着' => '那麼著',
'邱于庭' => '邱于庭',
'郁朴' => '郁樸',
'郁郁菲菲' => '郁郁菲菲',
'郁郁青青' => '郁郁青青',
'郊游' => '郊遊',
'郘钟' => '郘鐘',
+'部子里' => '部子里',
'部落发' => '部落發',
'郭后' => '郭后',
'都于' => '都於',
-'鄉愿' => '鄉愿',
'乡愿' => '鄉愿',
-'鄭凱云' => '鄭凱云',
+'鄉愿' => '鄉愿',
'郑凯云' => '鄭凱云',
-'郑庄公' => '鄭莊公',
+'鄭凱云' => '鄭凱云',
'配制饲料' => '配制飼料',
-'配合着' => '配合著',
+'配图里' => '配圖裡',
'配水干管' => '配水幹管',
-'配药' => '配藥',
'配制' => '配製',
'酒帘' => '酒帘',
+'酒气冲天' => '酒氣衝天',
'酒坛' => '酒罈',
'酒肴' => '酒肴',
-'酒药' => '酒藥',
-'酒醴曲蘖' => '酒醴麴櫱',
+'酒麹' => '酒麴',
'酒曲' => '酒麴',
'酥松' => '酥鬆',
-'酸碱' => '酸鹼',
+'酸姜' => '酸薑',
'醇朴' => '醇樸',
'醉于' => '醉於',
'醋坛' => '醋罈',
@@ -8907,12 +8331,13 @@ $zh2Hant = array(
'丑化' => '醜化',
'丑史' => '醜史',
'丑名' => '醜名',
-'丑咤' => '醜吒',
+'丑吒' => '醜吒',
'丑地' => '醜地',
'丑夷' => '醜夷',
'丑女' => '醜女',
'丑女效颦' => '醜女效顰',
'丑奴儿' => '醜奴兒',
+'丑婆子' => '醜婆子',
'丑妇' => '醜婦',
'丑媳' => '醜媳',
'丑媳妇' => '醜媳婦',
@@ -8948,18 +8373,18 @@ $zh2Hant = array(
'丑杂' => '醜雜',
'丑头怪脸' => '醜頭怪臉',
'丑类' => '醜類',
-'酝酿着' => '醞釀著',
-'医药' => '醫藥',
-'医院里' => '醫院裡',
'酿制' => '釀製',
'衅钟' => '釁鐘',
'采石之役' => '采石之役',
'采石之战' => '采石之戰',
'采石之戰' => '采石之戰',
-'采石磯' => '采石磯',
'采石矶' => '采石磯',
-'釉药' => '釉藥',
-'里程表' => '里程錶',
+'采石磯' => '采石磯',
+'里海大学' => '里海大學',
+'里海大學' => '里海大學',
+'里海崖' => '里海崖',
+'里海茨' => '里海茨',
+'里铺' => '里舖',
'重回' => '重回',
'重折' => '重摺',
'重于' => '重於',
@@ -8970,17 +8395,17 @@ $zh2Hant = array(
'重游' => '重遊',
'野姜' => '野薑',
'野游' => '野遊',
-'厘出' => '釐出',
-'厘升' => '釐升',
-'厘定' => '釐定',
+'量不准' => '量不準',
+'厘改' => '釐改',
+'厘整' => '釐整',
'厘正' => '釐正',
+'厘毫' => '釐毫',
'厘清' => '釐清',
'厘订' => '釐訂',
+'厘革' => '釐革',
'金仆姑' => '金僕姑',
-'金仑溪' => '金崙溪',
-'金布道' => '金布道',
+'金城里' => '金城里',
'金范' => '金範',
-'金色长发' => '金色長髮',
'金表情' => '金表情',
'金表态' => '金表態',
'金表扬' => '金表揚',
@@ -8992,12 +8417,10 @@ $zh2Hant = array(
'金表露' => '金表露',
'金表面' => '金表面',
'金装玉里' => '金裝玉裡',
-'金表' => '金錶',
+'金链' => '金鍊',
'金钟' => '金鐘',
-'金鸡纳碱' => '金雞納鹼',
-'金马仑道' => '金馬崙道',
'金发' => '金髮',
-'鈎心斗角' => '鈎心鬥角',
+'钩心斗角' => '鈎心鬥角',
'银朱' => '銀硃',
'银发' => '銀髮',
'铜范' => '銅範',
@@ -9005,37 +8428,27 @@ $zh2Hant = array(
'铜钟' => '銅鐘',
'铯钟' => '銫鐘',
'铝制' => '鋁製',
-'铺锦列绣' => '鋪錦列繡',
'钢之炼金术师' => '鋼之鍊金術師',
'钢梁' => '鋼樑',
'钢制' => '鋼製',
-'录着' => '錄著',
'录制' => '錄製',
'锤炼' => '錘鍊',
'钱谷' => '錢穀',
'钱范' => '錢範',
-'钱庄' => '錢莊',
-'锦綉花园' => '錦綉花園',
-'锦绣' => '錦繡',
+'锦卤' => '錦滷',
+'锦绣花园' => '錦綉花園',
'表停' => '錶停',
'表冠' => '錶冠',
'表带' => '錶帶',
-'表店' => '錶店',
-'表厂' => '錶廠',
'表快' => '錶快',
'表慢' => '錶慢',
'表板' => '錶板',
-'表壳' => '錶殼',
'表王' => '錶王',
-'表的嘀嗒' => '錶的嘀嗒',
-'表的历史' => '錶的歷史',
'表盘' => '錶盤',
'表蒙子' => '錶蒙子',
-'表行' => '錶行',
'表转' => '錶轉',
'表速' => '錶速',
'表针' => '錶針',
-'表链' => '錶鏈',
'炼冶' => '鍊冶',
'炼句' => '鍊句',
'炼字' => '鍊字',
@@ -9045,13 +8458,12 @@ $zh2Hant = array(
'炼气' => '鍊氣',
'炼汞' => '鍊汞',
'炼石' => '鍊石',
+'链表' => '鍊表',
'炼贫' => '鍊貧',
'炼金术' => '鍊金術',
-'炼钢' => '鍊鋼',
-'锅庄' => '鍋莊',
-'锻炼出' => '鍛鍊出',
'锲而不舍' => '鍥而不捨',
'镰仓' => '鎌倉',
+'镜图里' => '鏡圖裡',
'锈病' => '鏽病',
'锈菌' => '鏽菌',
'锈蚀' => '鏽蝕',
@@ -9080,7 +8492,6 @@ $zh2Hant = array(
'钟形虫' => '鐘形蟲',
'钟律' => '鐘律',
'钟快' => '鐘快',
-'钟意' => '鐘意',
'钟慢' => '鐘慢',
'钟摆' => '鐘擺',
'钟敲' => '鐘敲',
@@ -9111,12 +8522,8 @@ $zh2Hant = array(
'钟表停' => '鐘錶停',
'钟表快' => '鐘錶快',
'钟表慢' => '鐘錶慢',
-'钟表历史' => '鐘錶歷史',
'钟表王' => '鐘錶王',
-'钟表的' => '鐘錶的',
-'钟表的历史' => '鐘錶的歷史',
'钟表盘' => '鐘錶盤',
-'钟表行' => '鐘錶行',
'钟表速' => '鐘錶速',
'钟关' => '鐘關',
'钟陈列' => '鐘陳列',
@@ -9129,41 +8536,55 @@ $zh2Hant = array(
'钟点' => '鐘點',
'钟鼎' => '鐘鼎',
'钟鼓' => '鐘鼓',
-'铁杆' => '鐵杆',
-'铁栏杆' => '鐵欄杆',
'铁锈' => '鐵鏽',
'铁钟' => '鐵鐘',
'铸钟' => '鑄鐘',
+'鉴别' => '鑑別',
+'鉴古' => '鑑古',
+'鉴定' => '鑑定',
+'鉴察' => '鑑察',
+'鉴往知来' => '鑑往知來',
+'鉴戒' => '鑑戒',
+'鉴湖' => '鑑湖',
+'鉴藏' => '鑑藏',
+'鉴谅' => '鑑諒',
+'鉴证' => '鑑證',
+'鉴识' => '鑑識',
+'鉴赏' => '鑑賞',
'鉴于' => '鑒於',
'长几' => '長几',
'长于' => '長於',
'长历' => '長曆',
'长历史' => '長歷史',
-'长生药' => '長生藥',
+'长发公主' => '長髮公主',
+'长发妹' => '長髮妹',
+'长发姑娘' => '長髮姑娘',
'长胡' => '長鬍',
'门帘' => '門帘',
'门吊儿' => '門弔兒',
'门里' => '門裡',
'闫怀礼' => '閆懷禮',
+'開山辟谷' => '開山辟谷',
+'开山辟谷' => '開山闢谷',
'开吊' => '開弔',
'开征' => '開徵',
'开采' => '開採',
'开发' => '開發',
-'开药' => '開藥',
'开辟' => '開闢',
'开哄' => '開鬨',
+'闲邪' => '閑邪',
'闲情逸致' => '閒情逸緻',
'闲荡' => '閒蕩',
'闲游' => '閒遊',
'间不容发' => '間不容髮',
+'间里' => '間裡',
'闵采尔' => '閔採爾',
-'合府' => '閤府',
+'阁府' => '閤府',
'闺范' => '閨範',
'阃范' => '閫範',
'闯荡' => '闖蕩',
'闯炼' => '闖鍊',
'关系' => '關係',
-'关系着' => '關係著',
'关弓与我确' => '關弓與我确',
'关于' => '關於',
'辟佛' => '闢佛',
@@ -9179,37 +8600,34 @@ $zh2Hant = array(
'辟谣' => '闢謠',
'辟辟' => '闢辟',
'辟邪以律' => '闢邪以律',
-'防晒' => '防晒',
'防水表' => '防水錶',
'防御' => '防禦',
'防范' => '防範',
'防锈' => '防鏽',
'防台' => '防颱',
'阻于' => '阻於',
-'阿呆瓜' => '阿呆瓜',
-'阿斯图里亚斯' => '阿斯圖里亞斯',
-'阿呆' => '阿獃',
'阿里' => '阿里',
'附于' => '附於',
'附注' => '附註',
-'降压药' => '降壓藥',
'限制' => '限制',
-'升官' => '陞官',
-'除臭药' => '除臭藥',
+'院里' => '院裡',
'陪吊' => '陪弔',
'阴干' => '陰乾',
'阴历' => '陰曆',
'阴历史' => '陰歷史',
'阴沟里翻船' => '陰溝裡翻船',
'阴郁' => '陰鬱',
-'陈冲' => '陳冲',
'陳冲' => '陳冲',
+'陳士杰' => '陳士杰',
+'陈升' => '陳昇',
+'陳有后' => '陳有后',
'陈有后' => '陳有后',
'陈炼' => '陳鍊',
'陆游' => '陸遊',
'阳春面' => '陽春麵',
'阳历' => '陽曆',
'阳历史' => '陽歷史',
+'阳谷' => '陽穀',
'隆准许' => '隆准許',
'隆准' => '隆準',
'随于' => '隨於',
@@ -9224,12 +8642,14 @@ $zh2Hant = array(
'只身' => '隻身',
'雄斗斗' => '雄斗斗',
'雅范' => '雅範',
-'雅致' => '雅緻',
+'集数里' => '集數裡',
'集于' => '集於',
+'集里' => '集裡',
'集游法' => '集遊法',
'雕梁画栋' => '雕樑畫棟',
'双折射' => '雙折射',
'双折' => '雙摺',
+'双沟大曲' => '雙溝大麯',
'双胜类' => '雙胜類',
'双雕' => '雙鵰',
'杂合面儿' => '雜合麵兒',
@@ -9246,46 +8666,37 @@ $zh2Hant = array(
'离于' => '離於',
'难舍' => '難捨',
'难于' => '難於',
+'雨蒙蒙' => '雨濛濛',
'雪窗萤几' => '雪窗螢几',
'雪里' => '雪裡',
'雪里红' => '雪裡紅',
'雪里蕻' => '雪裡蕻',
-'云南白药' => '雲南白藥',
+'云吞面' => '雲吞麵',
'云笈七签' => '雲笈七籤',
'云游' => '雲遊',
'云须' => '雲鬚',
'零个' => '零個',
'零周后' => '零周後',
-'零多只' => '零多隻',
'零天后' => '零天後',
'零年' => '零年',
'零只' => '零隻',
'零余' => '零餘',
'电子表格' => '電子表格',
-'电子表' => '電子錶',
'电子钟' => '電子鐘',
'电子钟表' => '電子鐘錶',
-'电杆' => '電杆',
'电波钟' => '電波鐘',
'电码表' => '電碼表',
-'电线杆' => '電線杆',
'电冲' => '電衝',
'电表' => '電錶',
'电钟' => '電鐘',
'震栗' => '震慄',
-'震荡' => '震蕩',
+'霉气冲天' => '霉氣衝天',
'雾里' => '霧裡',
'露丑' => '露醜',
-'霸占' => '霸佔',
'霁范' => '霽範',
-'灵药' => '靈藥',
'青山一发' => '青山一髮',
-'青苹' => '青苹',
-'青苹果' => '青蘋果',
-'青蝇吊客' => '青蠅弔客',
-'青霉素' => '青霉素',
'青霉' => '青黴',
-'非占不可' => '非佔不可',
+'非常准' => '非常準',
'面包住' => '面包住',
'面包含' => '面包含',
'面包围' => '面包圍',
@@ -9299,64 +8710,68 @@ $zh2Hant = array(
'面包管' => '面包管',
'面包扎' => '面包紮',
'面包罗' => '面包羅',
-'面包着' => '面包著',
'面包藏' => '面包藏',
'面包装' => '面包裝',
'面包裹' => '面包裹',
'面包起' => '面包起',
'面包办' => '面包辦',
-'面店舖' => '面店舖',
-'面朝着' => '面朝著',
-'面条目' => '面條目',
+'面店铺' => '面店鋪',
'面條目' => '面條目',
+'面条目' => '面條目',
'面粉碎' => '面粉碎',
'面粉红' => '面粉紅',
-'面临着' => '面臨著',
'面食饭' => '面食飯',
'面食面' => '面食麵',
'鞋里' => '鞋裡',
'鞣制' => '鞣製',
'秋千' => '鞦韆',
'鞭辟入里' => '鞭辟入裡',
-'韦庄' => '韋莊',
+'韦席' => '韋蓆',
'韩国制' => '韓國製',
'韩制' => '韓製',
+'音不准' => '音不準',
'音准' => '音準',
'音声如钟' => '音聲如鐘',
'韶山冲' => '韶山沖',
'响钟' => '響鐘',
'頁面' => '頁面',
'页面' => '頁面',
-'頂多' => '頂多',
+'顶凶' => '頂兇',
'顶多' => '頂多',
-'项庄' => '項莊',
+'頂多' => '頂多',
+'项链' => '項鍊',
'顺于' => '順於',
'顺钟向' => '順鐘向',
'顺风后' => '順風後',
'须根据' => '須根據',
'颂系' => '頌繫',
'颂赞' => '頌讚',
+'预报不准' => '預報不準',
'预制' => '預製',
-'领域里' => '領域裡',
'领袖欲' => '領袖慾',
-'头巾吊在水里' => '頭巾弔在水裡',
+'头儿干' => '頭兒幹',
'头里' => '頭裡',
+'头长发' => '頭長髮',
'头发' => '頭髮',
'颊须' => '頰鬚',
-'题签' => '題籤',
'额征' => '額徵',
'额我略历' => '額我略曆',
'额我略历史' => '額我略歷史',
'颜范' => '顏範',
'颠干倒坤' => '顛乾倒坤',
-'颠覆' => '顛覆',
-'颠颠仆仆' => '顛顛仆仆',
'顛顛仆仆' => '顛顛仆仆',
+'颠颠仆仆' => '顛顛仆仆',
'颤栗' => '顫慄',
+'显示表明' => '顯示表明',
+'显示表格' => '顯示表格',
+'显示表现' => '顯示表現',
+'显示表示' => '顯示表示',
+'显示表达' => '顯示表達',
+'显示表面' => '顯示表面',
+'显示表头' => '顯示表頭',
'显示表' => '顯示錶',
'显示钟' => '顯示鐘',
'显示钟表' => '顯示鐘錶',
-'显著标志' => '顯著標志',
'风干' => '風乾',
'风后' => '風后',
'风土志' => '風土誌',
@@ -9366,8 +8781,8 @@ $zh2Hant = array(
'风范' => '風範',
'风里' => '風裡',
'风起云涌' => '風起雲湧',
-'风采' => '風采',
'風采' => '風采',
+'风采' => '風采',
'台风' => '颱風',
'台风后' => '颱風後',
'刮了' => '颳了',
@@ -9387,13 +8802,12 @@ $zh2Hant = array(
'飞行钟' => '飛行鐘',
'食欲' => '食慾',
'食欲不振' => '食欲不振',
-'食野之苹' => '食野之苹',
'食面' => '食麵',
'饭后钟' => '飯後鐘',
'饭团' => '飯糰',
-'饭庄' => '飯莊',
-'饲喂' => '飼餵',
'饼干' => '餅乾',
+'养脏' => '養臟',
+'餐台' => '餐檯',
'馂余' => '餕餘',
'余0' => '餘0',
'余1' => '餘1',
@@ -9440,6 +8854,8 @@ $zh2Hant = array(
'余子' => '餘子',
'余存' => '餘存',
'余孽' => '餘孽',
+'余年' => '餘年',
+'余式' => '餘式',
'余弦' => '餘弦',
'余思' => '餘思',
'余悸' => '餘悸',
@@ -9461,7 +8877,6 @@ $zh2Hant = array(
'余毒' => '餘毒',
'余气' => '餘氣',
'余波' => '餘波',
-'余波荡漾' => '餘波盪漾',
'余温' => '餘溫',
'余泽' => '餘澤',
'余沥' => '餘瀝',
@@ -9470,6 +8885,7 @@ $zh2Hant = array(
'余烬' => '餘燼',
'余珍' => '餘珍',
'余生' => '餘生',
+'余留' => '餘留',
'余众' => '餘眾',
'余窍' => '餘竅',
'余粮' => '餘糧',
@@ -9489,6 +8905,7 @@ $zh2Hant = array(
'余貾' => '餘貾',
'余辉' => '餘輝',
'余辜' => '餘辜',
+'余部' => '餘部',
'余酲' => '餘酲',
'余量' => '餘量',
'余闰' => '餘閏',
@@ -9497,82 +8914,48 @@ $zh2Hant = array(
'余震' => '餘震',
'余霞' => '餘霞',
'余音' => '餘音',
-'余音绕梁' => '餘音繞梁',
'余韵' => '餘韻',
'余响' => '餘響',
+'余项' => '餘項',
'余额' => '餘額',
'余风' => '餘風',
'余食' => '餘食',
'余党' => '餘黨',
-'余0' => '餘0',
-'余1' => '餘1',
-'余2' => '餘2',
-'余3' => '餘3',
-'余4' => '餘4',
-'余5' => '餘5',
-'余6' => '餘6',
-'余7' => '餘7',
-'余8' => '餘8',
-'余9' => '餘9',
'馄饨面' => '餛飩麵',
'馆谷' => '館穀',
'馆里' => '館裡',
-'喂乳' => '餵乳',
-'喂了' => '餵了',
-'喂奶' => '餵奶',
-'喂给' => '餵給',
-'喂羊' => '餵羊',
-'喂猪' => '餵豬',
-'喂过' => '餵過',
-'喂鸡' => '餵雞',
-'喂食' => '餵食',
-'喂饱' => '餵飽',
-'喂养' => '餵養',
-'喂驴' => '餵驢',
-'喂鱼' => '餵魚',
-'喂鸭' => '餵鴨',
-'喂鹅' => '餵鵝',
-'饑荒' => '饑荒',
-'饑馑' => '饑饉',
+'餵驴' => '餵驢',
+'饥寒' => '饑寒',
+'饥民' => '饑民',
+'饥渴' => '饑渴',
+'饥溺' => '饑溺',
+'饥荒' => '饑荒',
+'饥饱' => '饑飽',
+'饥馑' => '饑饉',
'首当其冲' => '首當其衝',
'首发' => '首發',
'首只' => '首隻',
+'首出电影' => '首齣電影',
'香干' => '香乾',
'香山庄' => '香山庄',
'马干' => '馬乾',
-'马占山' => '馬占山',
'馬占山' => '馬占山',
-'马杆' => '馬杆',
+'马斯垂克期' => '馬斯垂克期',
'馬格里布' => '馬格里布',
'马格里布' => '馬格里布',
-'马表' => '馬錶',
'驻扎' => '駐紮',
'骀荡' => '駘蕩',
-'腾格里' => '騰格里',
'騰格里' => '騰格里',
+'腾格里' => '騰格里',
+'腾涌' => '騰湧',
'腾冲' => '騰衝',
+'惊栗' => '驚慄',
'惊赞' => '驚讚',
'惊钟' => '驚鐘',
-'骨子里' => '骨子裡',
'骨干' => '骨幹',
'骨灰坛' => '骨灰罈',
'骨坛' => '骨罈',
-'骨头里挣出来的钱才做得肉' => '骨頭裡掙出來的錢纔做得肉',
-'肮肮脏脏' => '骯骯髒髒',
-'肮脏' => '骯髒',
-'脏乱' => '髒亂',
-'脏了' => '髒了',
-'脏兮兮' => '髒兮兮',
-'脏字' => '髒字',
-'脏得' => '髒得',
-'脏心' => '髒心',
-'脏东西' => '髒東西',
-'脏水' => '髒水',
-'脏的' => '髒的',
-'脏词' => '髒詞',
-'脏话' => '髒話',
-'脏钱' => '髒錢',
-'脏发' => '髒髮',
+'体征' => '體徵',
'体范' => '體範',
'体系' => '體系',
'高几' => '高几',
@@ -9620,7 +9003,7 @@ $zh2Hant = array(
'发胶' => '髮膠',
'发菜' => '髮菜',
'发蜡' => '髮蠟',
-'发踊冲冠' => '髮踊沖冠',
+'发踊冲冠' => '髮踴沖冠',
'发辫' => '髮辮',
'发针' => '髮針',
'发钗' => '髮釵',
@@ -9640,6 +9023,7 @@ $zh2Hant = array(
'松元音' => '鬆元音',
'松劲' => '鬆勁',
'松动' => '鬆動',
+'松化' => '鬆化',
'松口' => '鬆口',
'松喉' => '鬆喉',
'松土' => '鬆土',
@@ -9683,12 +9067,12 @@ $zh2Hant = array(
'须鲨' => '鬚鯊',
'须鲸' => '鬚鯨',
'鬓发' => '鬢髮',
-'斗上' => '鬥上',
'斗不过' => '鬥不過',
'斗了' => '鬥了',
'斗来斗去' => '鬥來鬥去',
'斗倒' => '鬥倒',
'斗分子' => '鬥分子',
+'斗剑' => '鬥劍',
'斗力' => '鬥力',
'斗劲' => '鬥勁',
'斗胜' => '鬥勝',
@@ -9739,13 +9123,12 @@ $zh2Hant = array(
'斗草' => '鬥草',
'斗叶儿' => '鬥葉兒',
'斗叶子' => '鬥葉子',
-'斗着' => '鬥著',
'斗蟋蟀' => '鬥蟋蟀',
'斗话' => '鬥話',
'斗艳' => '鬥豔',
'斗起' => '鬥起',
'斗趣' => '鬥趣',
-'斗闲气' => '鬥閑氣',
+'斗闲气' => '鬥閒氣',
'斗鸡' => '鬥雞',
'斗雪红' => '鬥雪紅',
'斗头' => '鬥頭',
@@ -9757,7 +9140,6 @@ $zh2Hant = array(
'斗鸭' => '鬥鴨',
'斗鹌鹑' => '鬥鵪鶉',
'斗丽' => '鬥麗',
-'闹着玩儿' => '鬧著玩兒',
'闹表' => '鬧錶',
'闹钟' => '鬧鐘',
'哄动' => '鬨動',
@@ -9800,8 +9182,10 @@ $zh2Hant = array(
'郁郁不乐' => '鬱鬱不樂',
'郁郁寡欢' => '鬱鬱寡歡',
'郁郁而终' => '鬱鬱而終',
+'郁郁苍苍' => '鬱鬱蒼蒼',
'郁郁葱葱' => '鬱鬱蔥蔥',
'郁黑' => '鬱黑',
+'鬼气冲天' => '鬼氣衝天',
'鬼谷子' => '鬼谷子',
'魂牵梦系' => '魂牽夢繫',
'魏征' => '魏徵',
@@ -9809,15 +9193,13 @@ $zh2Hant = array(
'魔表' => '魔錶',
'鱼干' => '魚乾',
'鱼松' => '魚鬆',
+'鲜于枢' => '鮮于樞',
+'鮮于樞' => '鮮于樞',
'鲸须' => '鯨鬚',
-'鯰鱼' => '鯰魚',
-'鸠占鹊巢' => '鳩佔鵲巢',
-'凤凰于飞' => '鳳凰于飛',
+'鳳凰于飛' => '鳳凰于飛',
'凤梨干' => '鳳梨乾',
'鸣钟' => '鳴鐘',
-'鸿案相庄' => '鴻案相莊',
'鸿范' => '鴻範',
-'鸿篇巨制' => '鴻篇巨製',
'鹅准' => '鵝準',
'鹄发' => '鵠髮',
'雕心雁爪' => '鵰心雁爪',
@@ -9826,6 +9208,7 @@ $zh2Hant = array(
'雕鹗' => '鵰鶚',
'鹤吊' => '鶴弔',
'鹤发' => '鶴髮',
+'鸾鉴' => '鸞鑑',
'鹰雕' => '鹰鵰',
'咸味' => '鹹味',
'咸嘴淡舌' => '鹹嘴淡舌',
@@ -9840,6 +9223,7 @@ $zh2Hant = array(
'咸湖' => '鹹湖',
'咸汤' => '鹹湯',
'咸潟' => '鹹潟',
+'咸湿' => '鹹濕',
'咸的' => '鹹的',
'咸粥' => '鹹粥',
'咸肉' => '鹹肉',
@@ -9853,32 +9237,18 @@ $zh2Hant = array(
'咸鸭蛋' => '鹹鴨蛋',
'咸卤' => '鹹鹵',
'咸咸' => '鹹鹹',
-'碱化' => '鹼化',
-'碱土金属' => '鹼土金屬',
-'碱地' => '鹼地',
-'碱度' => '鹼度',
-'碱性' => '鹼性',
-'碱水' => '鹼水',
-'碱液' => '鹼液',
-'碱熔' => '鹼熔',
-'碱石灰' => '鹼石灰',
-'碱纤维素' => '鹼纖維素',
-'碱金属' => '鹼金屬',
-'碱类' => '鹼類',
'盐打怎么咸' => '鹽打怎麼鹹',
+'盐卤' => '鹽滷',
'盐余' => '鹽餘',
-'盐碱土' => '鹽鹼土',
-'盐碱滩' => '鹽鹼灘',
+'鹿場里' => '鹿場里',
'丽于' => '麗於',
'曲尘' => '麴塵',
-'曲蘖' => '麴櫱',
-'曲生' => '麴生',
+'曲櫱' => '麴櫱',
'曲秀才' => '麴秀才',
-'曲菌' => '麴菌',
'曲车' => '麴車',
'曲道士' => '麴道士',
'曲钱' => '麴錢',
-'曲院' => '麴院',
+'麹霉' => '麴黴',
'曲霉' => '麴黴',
'面人儿' => '麵人兒',
'面价' => '麵價',
@@ -9901,19 +9271,16 @@ $zh2Hant = array(
'面粉' => '麵粉',
'面糊' => '麵糊',
'面团' => '麵糰',
-'面线' => '麵線',
'面缸' => '麵缸',
'面茶' => '麵茶',
'面食' => '麵食',
'面饺' => '麵餃',
'面饼' => '麵餅',
'面馆' => '麵館',
-'麻药' => '麻藥',
-'麻醉药' => '麻醉藥',
+'麻将席' => '麻將蓆',
'麻酱面' => '麻醬麵',
'黄干黑瘦' => '黃乾黑瘦',
'黄历' => '黃曆',
-'黄曲霉' => '黃曲霉',
'黄历史' => '黃歷史',
'黃詩杰' => '黃詩杰',
'黄诗杰' => '黃詩杰',
@@ -9923,14 +9290,16 @@ $zh2Hant = array(
'黄钟' => '黃鐘',
'黄发' => '黃髮',
'黄曲毒素' => '黃麴毒素',
-'黎吉云' => '黎吉雲',
+'黎克特制' => '黎克特制',
'黎吉雲' => '黎吉雲',
+'黎吉云' => '黎吉雲',
'黑奴吁天录' => '黑奴籲天錄',
-'黑色长发' => '黑色長髮',
'黑发' => '黑髮',
+'点札' => '點劄',
'点半钟' => '點半鐘',
'点多钟' => '點多鐘',
'点里' => '點裡',
+'点里程' => '點里程',
'点钟' => '點鐘',
'霉毒' => '黴毒',
'霉素' => '黴素',
@@ -9938,119 +9307,96 @@ $zh2Hant = array(
'霉黑' => '黴黑',
'霉黧' => '黴黧',
'鼓里' => '鼓裡',
+'鼓噪' => '鼓譟',
'冬冬鼓' => '鼕鼕鼓',
-'鼠药' => '鼠藥',
'鼠曲草' => '鼠麴草',
'鼻梁儿' => '鼻梁兒',
'鼻梁' => '鼻樑',
'鼻准' => '鼻準',
'齐王舍牛' => '齊王捨牛',
-'齐庄' => '齊莊',
'齿危发秀' => '齒危髮秀',
'齿落发白' => '齒落髮白',
'齿发' => '齒髮',
'出儿' => '齣兒',
-'出剧' => '齣劇',
-'出动画' => '齣動畫',
-'出卡通' => '齣卡通',
-'出戏' => '齣戲',
-'出节目' => '齣節目',
-'出电影' => '齣電影',
-'出电视' => '齣電視',
'龙卷' => '龍捲',
'龙眼干' => '龍眼乾',
'龙须' => '龍鬚',
+'龙须面' => '龍鬚麵',
'龙斗虎伤' => '龍鬥虎傷',
-'龟山庄' => '龜山庄',
-'!克制' => '!剋制',
-',克制' => ',剋制',
-'0周后' => '0周後',
-'0多只' => '0多隻',
-'0天后' => '0天後',
-'0年' => '0年',
-'0只' => '0隻',
-'0余' => '0餘',
-'1周后' => '1周後',
-'1天后' => '1天後',
-'1年' => '1年',
-'1只' => '1隻',
-'1余' => '1餘',
-'2周后' => '2周後',
-'2天后' => '2天後',
-'2年' => '2年',
-'2只' => '2隻',
-'2余' => '2餘',
-'3周后' => '3周後',
-'3天后' => '3天後',
-'3年' => '3年',
-'3只' => '3隻',
-'3余' => '3餘',
-'4周后' => '4周後',
-'4天后' => '4天後',
-'4年' => '4年',
-'4只' => '4隻',
-'4余' => '4餘',
-'5周后' => '5周後',
-'5天后' => '5天後',
-'5年' => '5年',
-'5只' => '5隻',
-'5余' => '5餘',
-'6周后' => '6周後',
-'6天后' => '6天後',
-'6年' => '6年',
-'6只' => '6隻',
-'6余' => '6餘',
-'7周后' => '7周後',
-'7天后' => '7天後',
-'7年' => '7年',
-'7只' => '7隻',
-'7余' => '7餘',
-'8周后' => '8周後',
-'8天后' => '8天後',
-'8年' => '8年',
-'8只' => '8隻',
-'8余' => '8餘',
-'9周后' => '9周後',
-'9天后' => '9天後',
-'9年' => '9年',
-'9只' => '9隻',
-'9余' => '9餘',
-':克制' => ':剋制',
-';克制' => ';剋制',
-'?克制' => '?剋制',
+'龜山庄' => '龜山庄',
+'龟鉴' => '龜鑑',
);
$zh2Hans = array(
'㑯' => '㑔',
'㑳' => '㑇',
+'㑶' => '㐹',
+'㑺' => '俊',
'㒓' => '𠉂',
+'㒺' => '罔',
+'㓂' => '寇',
'㓨' => '刾',
+'㕁' => '却',
+'㕑' => '厨',
+'㕘' => '参',
+'㕥' => '以',
'㗲' => '𠵾',
'㘚' => '㘎',
+'㘭' => '坳',
'㜄' => '㚯',
'㜏' => '㛣',
'㜢' => '𡞱',
'㜷' => '𡝠',
+'㝛' => '宿',
+'㝠' => '冥',
'㞞' => '𪨊',
+'㠀' => '岛',
'㠏' => '㟆',
+'㠯' => '以',
+'㠶' => '帆',
+'㡌' => '帽',
+'㢘' => '廉',
'㢝' => '𢋈',
+'㤙' => '恩',
+'㥦' => '惬',
+'㥫' => '惇',
'㥮' => '㤘',
'㦎' => '𢛯',
+'㧱' => '拿',
+'㨗' => '捷',
+'㨪' => '晃',
+'㨿' => '据',
+'㩗' => '携',
'㩜' => '㨫',
+'㩦' => '携',
'㩳' => '㧐',
+'㪚' => '散',
+'㪟' => '敦',
+'㬉' => '暖',
+'㬪' => '叠',
+'㯭' => '橹',
+'㱃' => '饮',
+'㳒' => '法',
+'㴱' => '深',
'㷿' => '𤈷',
'㺏' => '𤠋',
+'㼝' => '碗',
+'㽞' => '留',
+'㿜' => '瘪',
'㿧' => '𤽯',
'䀹' => '𥅴',
'䁪' => '𥇢',
'䁻' => '䀥',
+'䈰' => '筲',
'䉙' => '𥬀',
'䉬' => '𫂈',
'䉲' => '𥮜',
+'䊀' => '糊',
'䊭' => '𥺅',
'䊷' => '䌶',
'䋙' => '䌺',
'䋚' => '䌻',
+'䋹' => '䌿',
'䋻' => '䌾',
'䋿' => '𦈓',
'䌈' => '𦈖',
@@ -10060,15 +9406,26 @@ $zh2Hans = array(
'䌟' => '𦈞',
'䌥' => '𦈠',
'䌰' => '𦈙',
+'䎱' => '䎬',
'䕳' => '𦰴',
+'䗬' => '蜂',
'䗿' => '𧉞',
+'䘏' => '恤',
+'䘑' => '脉',
+'䘚' => '卒',
'䙡' => '䙌',
+'䛐' => '词',
+'䛡' => '话',
'䜀' => '䜧',
+'䝔' => '獾',
'䝻' => '𧹕',
'䝼' => '䞍',
'䞈' => '𧹑',
+'䠀' => '蹚',
+'䠶' => '射',
'䢨' => '𨑹',
'䥇' => '䦂',
+'䥥' => '镰',
'䥩' => '𨱖',
'䥱' => '䥾',
'䦘' => '𨸄',
@@ -10080,11 +9437,13 @@ $zh2Hans = array(
'䪗' => '𩐀',
'䪘' => '𩏿',
'䫴' => '𩖗',
+'䬃' => '飒',
'䬘' => '𩙮',
'䬝' => '𩙯',
'䬞' => '𩙧',
'䭀' => '𩠇',
'䭃' => '𩠈',
+'䭾' => '驮',
'䭿' => '𩧭',
'䮝' => '𩧰',
'䮞' => '𩨁',
@@ -10093,6 +9452,7 @@ $zh2Hans = array(
'䮳' => '𩨏',
'䮾' => '𩧪',
'䯀' => '䯅',
+'䰟' => '魂',
'䰾' => '鲃',
'䱙' => '𩾈',
'䱬' => '𩾊',
@@ -10101,16 +9461,27 @@ $zh2Hans = array(
'䱽' => '䲝',
'䲁' => '鳚',
'䲖' => '𩾂',
+'䲘' => '鳤',
'䲰' => '𪉂',
+'䳘' => '鹅',
'䴉' => '鹮',
'䴬' => '𪎈',
'䴴' => '𪎋',
+'䶊' => '衄',
'丟' => '丢',
+'丣' => '卯',
'並' => '并',
+'乗' => '乘',
+'乹' => '干',
'乾' => '干',
+'亁' => '干',
'亂' => '乱',
'亙' => '亘',
+'亝' => '斋',
'亞' => '亚',
+'亱' => '夜',
+'亷' => '廉',
+'亾' => '亡',
'佇' => '伫',
'佈' => '布',
'佔' => '占',
@@ -10118,29 +9489,36 @@ $zh2Hans = array(
'來' => '来',
'侖' => '仑',
'侶' => '侣',
+'侷' => '局',
'俁' => '俣',
'係' => '系',
'俔' => '伣',
'俠' => '侠',
'俥' => '伡',
+'俻' => '备',
'倀' => '伥',
'倆' => '俩',
'倈' => '俫',
'倉' => '仓',
'個' => '个',
+'倐' => '倏',
'們' => '们',
'倖' => '幸',
+'倣' => '仿',
'倫' => '伦',
'倲' => '㑈',
+'倸' => '睬',
'偉' => '伟',
'偑' => '㐽',
'側' => '侧',
'偵' => '侦',
'偽' => '伪',
+'傌' => '㐷',
'傑' => '杰',
'傖' => '伧',
'傘' => '伞',
'備' => '备',
+'傚' => '效',
'傢' => '家',
'傭' => '佣',
'傯' => '偬',
@@ -10152,6 +9530,8 @@ $zh2Hans = array(
'僂' => '偻',
'僅' => '仅',
'僉' => '佥',
+'僊' => '仙',
+'働' => '动',
'僑' => '侨',
'僕' => '仆',
'僞' => '伪',
@@ -10164,6 +9544,7 @@ $zh2Hans = array(
'億' => '亿',
'儈' => '侩',
'儉' => '俭',
+'儌' => '侥',
'儐' => '傧',
'儔' => '俦',
'儕' => '侪',
@@ -10179,32 +9560,49 @@ $zh2Hans = array(
'儼' => '俨',
'兇' => '凶',
'兌' => '兑',
+'兎' => '兔',
'兒' => '儿',
'兗' => '兖',
+'兠' => '兜',
'內' => '内',
'兩' => '两',
+'冄' => '冉',
'冊' => '册',
+'冐' => '冒',
+'冑' => '胄',
'冪' => '幂',
+'冺' => '泯',
'凈' => '净',
'凍' => '冻',
'凙' => '𪞝',
'凜' => '凛',
+'凢' => '凡',
'凱' => '凯',
+'凴' => '凭',
'別' => '别',
+'刦' => '劫',
+'刧' => '劫',
'刪' => '删',
+'刼' => '劫',
'剄' => '刭',
'則' => '则',
+'剉' => '锉',
'剋' => '克',
'剎' => '刹',
+'剏' => '创',
'剗' => '刬',
+'剙' => '创',
'剛' => '刚',
'剝' => '剥',
'剮' => '剐',
+'剳' => '札',
'剴' => '剀',
'創' => '创',
'剷' => '铲',
+'剹' => '戮',
'剾' => '𠛅',
'劃' => '划',
+'劄' => '札',
'劇' => '剧',
'劉' => '刘',
'劊' => '刽',
@@ -10212,48 +9610,73 @@ $zh2Hans = array(
'劍' => '剑',
'劏' => '㓥',
'劑' => '剂',
+'劒' => '剑',
'劚' => '㔉',
+'効' => '效',
'勁' => '劲',
+'勅' => '敕',
+'勌' => '倦',
+'勑' => '敕',
'動' => '动',
'務' => '务',
'勛' => '勋',
'勝' => '胜',
'勞' => '劳',
'勢' => '势',
+'勦' => '剿',
'勩' => '勚',
'勱' => '劢',
'勳' => '勋',
'勵' => '励',
'勸' => '劝',
'勻' => '匀',
+'匟' => '炕',
'匭' => '匦',
'匯' => '汇',
'匱' => '匮',
+'匲' => '奁',
+'匳' => '奁',
'區' => '区',
'協' => '协',
+'卹' => '恤',
'卻' => '却',
'卽' => '即',
+'厀' => '膝',
'厙' => '厍',
+'厠' => '厕',
'厤' => '历',
'厭' => '厌',
+'厰' => '厂',
'厲' => '厉',
'厴' => '厣',
'參' => '参',
'叄' => '叁',
'叢' => '丛',
-'吒' => '咤',
+'吚' => '咿',
'吳' => '吴',
'吶' => '呐',
'呂' => '吕',
+'呌' => '叫',
+'呪' => '咒',
+'咊' => '和',
'咼' => '呙',
'員' => '员',
'哯' => '𠯟',
+'哶' => '咩',
'唄' => '呗',
+'唕' => '唣',
+'唘' => '启',
'唚' => '吣',
+'唸' => '念',
+'啎' => '忤',
'問' => '问',
+'啑' => '喋',
+'啓' => '启',
+'啗' => '啖',
'啞' => '哑',
'啟' => '启',
'啢' => '唡',
+'啣' => '衔',
'喎' => '㖞',
'喚' => '唤',
'喪' => '丧',
@@ -10261,6 +9684,7 @@ $zh2Hans = array(
'喬' => '乔',
'單' => '单',
'喲' => '哟',
+'嗁' => '啼',
'嗆' => '呛',
'嗇' => '啬',
'嗊' => '唝',
@@ -10269,22 +9693,26 @@ $zh2Hans = array(
'嗩' => '唢',
'嗶' => '哔',
'嗹' => '𪡏',
+'嘅' => '慨',
'嘆' => '叹',
'嘍' => '喽',
+'嘑' => '呼',
'嘓' => '啯',
'嘔' => '呕',
'嘖' => '啧',
'嘗' => '尝',
'嘜' => '唛',
+'嘠' => '嘎',
'嘩' => '哗',
'嘮' => '唠',
'嘯' => '啸',
'嘰' => '叽',
'嘵' => '哓',
+'嘷' => '嗥',
'嘸' => '呒',
'嘽' => '啴',
-'噁' => '恶',
'噅' => '𠯠',
+'噉' => '啖',
'噓' => '嘘',
'噚' => '㖊',
'噝' => '咝',
@@ -10318,7 +9746,10 @@ $zh2Hans = array(
'囉' => '啰',
'囌' => '苏',
'囑' => '嘱',
+'囓' => '啮',
+'囙' => '因',
'囪' => '囱',
+'圅' => '函',
'圇' => '囵',
'國' => '国',
'圍' => '围',
@@ -10327,6 +9758,8 @@ $zh2Hans = array(
'圖' => '图',
'團' => '团',
'圞' => '𪢮',
+'坿' => '附',
+'垜' => '垛',
'垵' => '埯',
'埡' => '垭',
'埰' => '采',
@@ -10344,19 +9777,24 @@ $zh2Hans = array(
'塒' => '埘',
'塗' => '涂',
'塚' => '冢',
+'塟' => '葬',
'塢' => '坞',
'塤' => '埙',
+'塲' => '场',
'塵' => '尘',
'塹' => '堑',
'墊' => '垫',
+'墖' => '塔',
'墜' => '坠',
'墮' => '堕',
'墰' => '坛',
'墳' => '坟',
+'墻' => '墙',
'墾' => '垦',
'壇' => '坛',
'壈' => '𡒄',
'壋' => '垱',
+'壎' => '埙',
'壓' => '压',
'壘' => '垒',
'壙' => '圹',
@@ -10369,8 +9807,10 @@ $zh2Hans = array(
'壩' => '坝',
'壯' => '壮',
'壺' => '壶',
+'壻' => '婿',
'壼' => '壸',
'壽' => '寿',
+'夘' => '卯',
'夠' => '够',
'夢' => '梦',
'夥' => '伙',
@@ -10379,78 +9819,111 @@ $zh2Hans = array(
'奧' => '奥',
'奩' => '奁',
'奪' => '夺',
+'奬' => '奖',
'奮' => '奋',
'奼' => '姹',
'妝' => '妆',
+'妬' => '妒',
+'妳' => '你',
+'妷' => '侄',
+'姉' => '姊',
'姍' => '姗',
+'姙' => '妊',
'姦' => '奸',
+'姪' => '侄',
+'姸' => '妍',
'娛' => '娱',
'婁' => '娄',
+'婣' => '姻',
'婦' => '妇',
+'婬' => '淫',
'婭' => '娅',
+'媍' => '妇',
'媧' => '娲',
'媯' => '妫',
'媰' => '㛀',
'媼' => '媪',
'媽' => '妈',
+'媿' => '愧',
+'嫋' => '袅',
'嫗' => '妪',
+'嫰' => '嫩',
'嫵' => '妩',
+'嫺' => '娴',
'嫻' => '娴',
'嫿' => '婳',
+'嬀' => '妫',
'嬃' => '媭',
'嬈' => '娆',
'嬋' => '婵',
'嬌' => '娇',
'嬙' => '嫱',
+'嬝' => '袅',
'嬡' => '嫒',
'嬤' => '嬷',
'嬪' => '嫔',
+'嬭' => '奶',
'嬰' => '婴',
'嬸' => '婶',
+'嬾' => '懒',
+'孃' => '娘',
'孋' => '㛤',
'孌' => '娈',
'孫' => '孙',
'學' => '学',
+'孼' => '孽',
'孿' => '孪',
+'宂' => '冗',
'宮' => '宫',
'寀' => '采',
+'寃' => '冤',
+'寑' => '寝',
'寢' => '寝',
'實' => '实',
'寧' => '宁',
'審' => '审',
'寫' => '写',
'寬' => '宽',
+'寳' => '宝',
'寵' => '宠',
'寶' => '宝',
+'尅' => '克',
'將' => '将',
'專' => '专',
'尋' => '寻',
'對' => '对',
'導' => '导',
+'尒' => '尔',
+'尙' => '尚',
+'尟' => '鲜',
+'尠' => '鲜',
'尷' => '尴',
'屆' => '届',
'屍' => '尸',
'屓' => '屃',
+'屛' => '屏',
'屜' => '屉',
'屢' => '屡',
'層' => '层',
'屨' => '屦',
'屩' => '𪨗',
'屬' => '属',
+'屭' => '屃',
+'岅' => '坂',
'岡' => '冈',
+'峝' => '峒',
'峴' => '岘',
'島' => '岛',
'峽' => '峡',
'崍' => '崃',
-'崑' => '昆',
'崗' => '岗',
-'崙' => '仑',
'崢' => '峥',
'崬' => '岽',
'嵐' => '岚',
'嵗' => '岁',
'嵼' => '𡶴',
'嶁' => '嵝',
+'嶃' => '崭',
'嶄' => '崭',
'嶇' => '岖',
'嶔' => '嵚',
@@ -10458,6 +9931,7 @@ $zh2Hans = array(
'嶠' => '峤',
'嶢' => '峣',
'嶧' => '峄',
+'嶨' => '峃',
'嶮' => '崄',
'嶴' => '岙',
'嶸' => '嵘',
@@ -10468,28 +9942,40 @@ $zh2Hans = array(
'巒' => '峦',
'巔' => '巅',
'巖' => '岩',
+'巗' => '岩',
'巰' => '巯',
-'巹' => '卺',
+'巵' => '卮',
+'帀' => '匝',
+'帋' => '纸',
'帥' => '帅',
'師' => '师',
+'帬' => '裙',
'帳' => '帐',
'帶' => '带',
'幀' => '帧',
'幃' => '帏',
+'幇' => '帮',
+'幑' => '徽',
'幗' => '帼',
'幘' => '帻',
+'幙' => '幕',
+'幚' => '帮',
'幟' => '帜',
'幣' => '币',
'幫' => '帮',
'幬' => '帱',
'幹' => '干',
-'幺' => '么',
'幾' => '几',
'庫' => '库',
+'庻' => '庶',
+'庽' => '寓',
'廁' => '厕',
'廂' => '厢',
'廄' => '厩',
'廈' => '厦',
+'廎' => '庼',
+'廐' => '厩',
+'廕' => '荫',
'廚' => '厨',
'廝' => '厮',
'廟' => '庙',
@@ -10500,6 +9986,10 @@ $zh2Hans = array(
'廩' => '廪',
'廬' => '庐',
'廳' => '厅',
+'廵' => '巡',
+'廹' => '迫',
+'廻' => '回',
+'廼' => '乃',
'弒' => '弑',
'弔' => '吊',
'弳' => '弪',
@@ -10513,45 +10003,63 @@ $zh2Hans = array(
'彞' => '彝',
'彠' => '彟',
'彥' => '彦',
+'彫' => '雕',
'彲' => '彨',
+'徃' => '往',
'後' => '后',
'徑' => '径',
'從' => '从',
'徠' => '徕',
+'徧' => '遍',
'復' => '复',
'徵' => '征',
'徹' => '彻',
+'怱' => '匆',
+'怳' => '恍',
'恆' => '恒',
+'恠' => '怪',
+'恡' => '吝',
'恥' => '耻',
'悅' => '悦',
'悞' => '悮',
+'悤' => '匆',
'悵' => '怅',
'悶' => '闷',
+'悽' => '凄',
+'惏' => '婪',
'惡' => '恶',
+'惥' => '恿',
'惱' => '恼',
'惲' => '恽',
+'惷' => '蠢',
'惻' => '恻',
'愛' => '爱',
'愜' => '惬',
'愨' => '悫',
'愴' => '怆',
'愷' => '恺',
+'愽' => '博',
'愾' => '忾',
'慄' => '栗',
'態' => '态',
'慍' => '愠',
'慘' => '惨',
+'慙' => '惭',
'慚' => '惭',
'慟' => '恸',
'慣' => '惯',
+'慤' => '悫',
'慪' => '怄',
'慫' => '怂',
'慮' => '虑',
'慳' => '悭',
+'慴' => '慑',
'慶' => '庆',
'慼' => '戚',
+'慽' => '戚',
'慾' => '欲',
'憂' => '忧',
+'憇' => '憩',
'憊' => '惫',
'憐' => '怜',
'憑' => '凭',
@@ -10581,20 +10089,26 @@ $zh2Hans = array(
'戀' => '恋',
'戇' => '戆',
'戔' => '戋',
+'戞' => '戛',
'戧' => '戗',
'戩' => '戬',
+'戯' => '戏',
'戰' => '战',
'戱' => '戯',
'戲' => '戏',
'戶' => '户',
+'戹' => '厄',
+'扞' => '捍',
+'抝' => '拗',
'拋' => '抛',
'拚' => '拼',
'挩' => '捝',
'挱' => '挲',
+'挵' => '弄',
'挾' => '挟',
+'捄' => '救',
'捨' => '舍',
'捫' => '扪',
-'捱' => '挨',
'捲' => '卷',
'掃' => '扫',
'掄' => '抡',
@@ -10603,15 +10117,29 @@ $zh2Hans = array(
'掙' => '挣',
'掛' => '挂',
'採' => '采',
+'掽' => '碰',
'揀' => '拣',
+'揑' => '捏',
'揚' => '扬',
'換' => '换',
+'揫' => '揪',
'揮' => '挥',
+'揷' => '插',
+'揹' => '背',
+'搆' => '构',
+'搇' => '揿',
+'搉' => '榷',
'損' => '损',
'搖' => '摇',
'搗' => '捣',
+'搤' => '扼',
+'搥' => '捶',
+'搨' => '拓',
+'搯' => '掏',
'搵' => '揾',
'搶' => '抢',
+'搾' => '榨',
+'摃' => '扛',
'摋' => '𢫬',
'摑' => '掴',
'摜' => '掼',
@@ -10627,8 +10155,10 @@ $zh2Hans = array(
'撓' => '挠',
'撝' => '㧑',
'撟' => '挢',
+'撡' => '操',
'撣' => '掸',
'撥' => '拨',
+'撦' => '扯',
'撫' => '抚',
'撲' => '扑',
'撳' => '揿',
@@ -10642,9 +10172,11 @@ $zh2Hans = array(
'擋' => '挡',
'擓' => '㧟',
'擔' => '担',
+'擕' => '携',
'據' => '据',
'擠' => '挤',
'擣' => '𢭏',
+'擧' => '举',
'擬' => '拟',
'擯' => '摈',
'擰' => '拧',
@@ -10669,12 +10201,17 @@ $zh2Hans = array(
'攢' => '攒',
'攣' => '挛',
'攤' => '摊',
+'攩' => '挡',
'攪' => '搅',
'攬' => '揽',
+'攷' => '考',
+'敂' => '叩',
+'敍' => '叙',
'敗' => '败',
'敘' => '叙',
'敵' => '敌',
'數' => '数',
+'敺' => '驱',
'斂' => '敛',
'斃' => '毙',
'斅' => '𢽾',
@@ -10685,7 +10222,10 @@ $zh2Hans = array(
'於' => '于',
'旂' => '旗',
'旣' => '既',
-'昇' => '升',
+'旤' => '祸',
+'旹' => '时',
+'旾' => '春',
+'昬' => '昏',
'時' => '时',
'晉' => '晋',
'晝' => '昼',
@@ -10694,24 +10234,38 @@ $zh2Hans = array(
'暘' => '旸',
'暢' => '畅',
'暫' => '暂',
+'暱' => '昵',
'曄' => '晔',
'曆' => '历',
'曇' => '昙',
'曉' => '晓',
-'曏' => '向',
'曖' => '暧',
'曠' => '旷',
+'曡' => '叠',
'曥' => '𣆐',
'曨' => '昽',
'曬' => '晒',
'書' => '书',
'會' => '会',
+'朞' => '期',
+'朢' => '望',
'朥' => '𦛨',
'朧' => '胧',
'朮' => '术',
+'朶' => '朵',
'東' => '东',
'杴' => '锨',
+'枱' => '台',
'柵' => '栅',
+'柺' => '拐',
+'査' => '查',
+'栁' => '柳',
+'栞' => '刊',
+'栢' => '柏',
+'栰' => '筏',
+'桒' => '桑',
+'桮' => '杯',
+'桺' => '柳',
'桿' => '杆',
'梔' => '栀',
'梘' => '枧',
@@ -10727,10 +10281,17 @@ $zh2Hans = array(
'棧' => '栈',
'棲' => '栖',
'棶' => '梾',
+'椀' => '碗',
+'椉' => '乘',
'椏' => '桠',
+'椗' => '碇',
'椲' => '㭏',
+'椶' => '棕',
+'椷' => '缄',
+'椾' => '笺',
'楊' => '杨',
'楓' => '枫',
+'楥' => '楦',
'楨' => '桢',
'業' => '业',
'極' => '极',
@@ -10742,9 +10303,11 @@ $zh2Hans = array(
'構' => '构',
'槍' => '枪',
'槓' => '杠',
+'槕' => '桌',
'槤' => '梿',
'槧' => '椠',
'槨' => '椁',
+'槮' => '椮',
'槳' => '桨',
'槶' => '椢',
'槼' => '椝',
@@ -10765,6 +10328,7 @@ $zh2Hans = array(
'樿' => '椫',
'橈' => '桡',
'橋' => '桥',
+'橜' => '橛',
'機' => '机',
'橢' => '椭',
'橫' => '横',
@@ -10782,6 +10346,7 @@ $zh2Hans = array(
'檸' => '柠',
'檻' => '槛',
'櫃' => '柜',
+'櫈' => '凳',
'櫓' => '橹',
'櫚' => '榈',
'櫛' => '栉',
@@ -10806,16 +10371,21 @@ $zh2Hans = array(
'欒' => '栾',
'欓' => '𣗋',
'欖' => '榄',
+'欝' => '郁',
'欞' => '棂',
+'欵' => '款',
'欽' => '钦',
'歎' => '叹',
'歐' => '欧',
+'歛' => '敛',
'歟' => '欤',
'歡' => '欢',
'歲' => '岁',
+'歴' => '历',
'歷' => '历',
'歸' => '归',
'歿' => '殁',
+'殀' => '夭',
'殘' => '残',
'殞' => '殒',
'殤' => '殇',
@@ -10827,18 +10397,24 @@ $zh2Hans = array(
'殰' => '㱩',
'殲' => '歼',
'殺' => '杀',
+'殻' => '壳',
'殼' => '壳',
'毀' => '毁',
'毆' => '殴',
+'毧' => '绒',
+'毬' => '球',
'毿' => '毵',
'氂' => '牦',
'氈' => '毡',
+'氊' => '毡',
'氌' => '氇',
'氣' => '气',
'氫' => '氢',
'氬' => '氩',
'氳' => '氲',
+'氷' => '冰',
'汙' => '污',
+'汚' => '污',
'決' => '决',
'沒' => '没',
'沖' => '冲',
@@ -10848,9 +10424,11 @@ $zh2Hans = array(
'洶' => '汹',
'浹' => '浃',
'涇' => '泾',
+'涖' => '莅',
'涼' => '凉',
'淒' => '凄',
'淚' => '泪',
+'淛' => '浙',
'淥' => '渌',
'淨' => '净',
'淩' => '凌',
@@ -10868,19 +10446,25 @@ $zh2Hans = array(
'湞' => '浈',
'湧' => '涌',
'湯' => '汤',
+'湻' => '淳',
+'湼' => '涅',
'溈' => '沩',
'準' => '准',
'溝' => '沟',
'溫' => '温',
+'溮' => '浉',
'溳' => '涢',
+'溼' => '湿',
'滄' => '沧',
'滅' => '灭',
'滌' => '涤',
'滎' => '荥',
'滙' => '汇',
+'滛' => '淫',
'滬' => '沪',
'滯' => '滞',
'滲' => '渗',
+'滷' => '卤',
'滸' => '浒',
'滻' => '浐',
'滾' => '滚',
@@ -10896,8 +10480,10 @@ $zh2Hans = array(
'漸' => '渐',
'漿' => '浆',
'潁' => '颍',
+'潄' => '漱',
'潑' => '泼',
'潔' => '洁',
+'潙' => '沩',
'潛' => '潜',
'潤' => '润',
'潯' => '浔',
@@ -10905,6 +10491,7 @@ $zh2Hans = array(
'潷' => '滗',
'潿' => '涠',
'澀' => '涩',
+'澁' => '涩',
'澅' => '𣶩',
'澆' => '浇',
'澇' => '涝',
@@ -10921,6 +10508,7 @@ $zh2Hans = array(
'濃' => '浓',
'濄' => '㳡',
'濆' => '𣸣',
+'濇' => '涩',
'濕' => '湿',
'濘' => '泞',
'濜' => '浕',
@@ -10930,6 +10518,7 @@ $zh2Hans = array(
'濫' => '滥',
'濰' => '潍',
'濱' => '滨',
+'濶' => '阔',
'濺' => '溅',
'濼' => '泺',
'濾' => '滤',
@@ -10954,6 +10543,7 @@ $zh2Hans = array(
'瀾' => '澜',
'灃' => '沣',
'灄' => '滠',
+'灋' => '法',
'灑' => '洒',
'灕' => '漓',
'灘' => '滩',
@@ -10964,13 +10554,17 @@ $zh2Hans = array(
'灣' => '湾',
'灤' => '滦',
'灧' => '滟',
+'灩' => '滟',
'災' => '灾',
'為' => '为',
'烏' => '乌',
+'烖' => '灾',
'烴' => '烃',
'無' => '无',
'煉' => '炼',
+'煑' => '煮',
'煒' => '炜',
+'煗' => '暖',
'煙' => '烟',
'煢' => '茕',
'煥' => '焕',
@@ -10978,6 +10572,7 @@ $zh2Hans = array(
'煬' => '炀',
'煱' => '㶽',
'熅' => '煴',
+'熈' => '熙',
'熉' => '𤈶',
'熌' => '𤇄',
'熒' => '荧',
@@ -10988,7 +10583,9 @@ $zh2Hans = array(
'熲' => '颎',
'熾' => '炽',
'燁' => '烨',
+'燄' => '焰',
'燈' => '灯',
+'燉' => '炖',
'燒' => '烧',
'燙' => '烫',
'燜' => '焖',
@@ -10998,26 +10595,39 @@ $zh2Hans = array(
'燭' => '烛',
'燴' => '烩',
'燶' => '㶶',
+'燻' => '熏',
'燼' => '烬',
'燾' => '焘',
'爄' => '𤇃',
'爍' => '烁',
'爐' => '炉',
+'爗' => '烨',
'爛' => '烂',
'爭' => '争',
'爲' => '为',
'爺' => '爷',
'爾' => '尔',
+'牀' => '床',
'牆' => '墙',
+'牋' => '笺',
+'牎' => '窗',
+'牐' => '闸',
+'牓' => '榜',
+'牕' => '窗',
'牘' => '牍',
+'牠' => '它',
+'牴' => '抵',
'牽' => '牵',
'犖' => '荦',
'犢' => '犊',
'犧' => '牺',
'狀' => '状',
+'狥' => '徇',
'狹' => '狭',
'狽' => '狈',
+'猂' => '悍',
'猙' => '狰',
+'猨' => '猿',
'猶' => '犹',
'猻' => '狲',
'獁' => '犸',
@@ -11025,6 +10635,8 @@ $zh2Hans = array(
'獄' => '狱',
'獅' => '狮',
'獎' => '奖',
+'獘' => '毙',
+'獧' => '狷',
'獨' => '独',
'獪' => '狯',
'獫' => '猃',
@@ -11040,23 +10652,32 @@ $zh2Hans = array(
'獼' => '猕',
'玀' => '猡',
'玁' => '𤞤',
+'玅' => '妙',
'現' => '现',
+'琖' => '盏',
+'琱' => '雕',
'琺' => '珐',
'琿' => '珲',
+'瑇' => '玳',
'瑋' => '玮',
'瑒' => '玚',
'瑣' => '琐',
'瑤' => '瑶',
'瑩' => '莹',
'瑪' => '玛',
+'瑯' => '琅',
'瑲' => '玱',
'瑽' => '𪻐',
'璉' => '琏',
+'璡' => '琎',
+'璢' => '瑠',
'璣' => '玑',
'璦' => '瑷',
'璫' => '珰',
'璯' => '㻅',
'環' => '环',
+'璵' => '玙',
+'璸' => '瑸',
'璽' => '玺',
'瓊' => '琼',
'瓏' => '珑',
@@ -11064,34 +10685,52 @@ $zh2Hans = array(
'瓕' => '𤦀',
'瓚' => '瓒',
'甌' => '瓯',
+'甎' => '砖',
'甕' => '瓮',
+'甖' => '罂',
+'甞' => '尝',
'產' => '产',
-'甦' => '苏',
-'甯' => '宁',
+'産' => '产',
+'畂' => '亩',
+'畆' => '亩',
'畝' => '亩',
'畢' => '毕',
+'畧' => '略',
'畫' => '画',
+'畮' => '亩',
'異' => '异',
+'畱' => '留',
'畵' => '画',
'當' => '当',
'疇' => '畴',
'疊' => '叠',
+'疎' => '疏',
+'疘' => '肛',
+'疿' => '痱',
+'痐' => '蛔',
'痙' => '痉',
'痠' => '酸',
-'痾' => '疴',
+'痲' => '痳',
+'痺' => '痹',
'瘂' => '痖',
+'瘉' => '愈',
'瘋' => '疯',
'瘍' => '疡',
'瘓' => '痪',
+'瘖' => '喑',
'瘞' => '瘗',
'瘡' => '疮',
'瘧' => '疟',
'瘮' => '瘆',
'瘲' => '疭',
'瘺' => '瘘',
+'瘻' => '瘘',
'療' => '疗',
+'癄' => '憔',
+'癅' => '瘤',
'癆' => '痨',
'癇' => '痫',
+'癈' => '废',
'癉' => '瘅',
'癒' => '愈',
'癘' => '疠',
@@ -11109,12 +10748,18 @@ $zh2Hans = array(
'癱' => '瘫',
'癲' => '癫',
'發' => '发',
+'皁' => '皂',
+'皐' => '皋',
'皚' => '皑',
+'皜' => '皓',
'皟' => '𤾀',
'皰' => '疱',
+'皷' => '鼓',
'皸' => '皲',
'皺' => '皱',
'盃' => '杯',
+'盇' => '盍',
+'盌' => '碗',
'盜' => '盗',
'盞' => '盏',
'盡' => '尽',
@@ -11122,13 +10767,19 @@ $zh2Hans = array(
'盤' => '盘',
'盧' => '卢',
'盪' => '荡',
+'眎' => '视',
'眞' => '真',
+'眡' => '视',
'眥' => '眦',
'眾' => '众',
'睍' => '𪾢',
'睏' => '困',
'睜' => '睁',
'睞' => '睐',
+'睠' => '眷',
+'睪' => '睾',
+'瞇' => '眯',
+'瞖' => '翳',
'瞘' => '眍',
'瞜' => '䁖',
'瞞' => '瞒',
@@ -11136,18 +10787,21 @@ $zh2Hans = array(
'瞭' => '了',
'瞶' => '瞆',
'瞼' => '睑',
+'矁' => '瞅',
'矇' => '蒙',
'矓' => '眬',
+'矙' => '瞰',
'矚' => '瞩',
'矯' => '矫',
+'砲' => '炮',
'硃' => '朱',
'硜' => '硁',
'硤' => '硖',
'硨' => '砗',
'硯' => '砚',
-'碕' => '埼',
'碙' => '𥐻',
'碩' => '硕',
+'碪' => '砧',
'碭' => '砀',
'碸' => '砜',
'確' => '确',
@@ -11155,13 +10809,14 @@ $zh2Hans = array(
'碽' => '䂵',
'磑' => '硙',
'磚' => '砖',
+'磟' => '碌',
'磠' => '硵',
'磣' => '碜',
'磧' => '碛',
'磯' => '矶',
'磽' => '硗',
'礄' => '硚',
-'礆' => '硷',
+'礆' => '碱',
'礎' => '础',
'礒' => '𥐟',
'礙' => '碍',
@@ -11169,7 +10824,9 @@ $zh2Hans = array(
'礪' => '砺',
'礫' => '砾',
'礬' => '矾',
+'礮' => '炮',
'礱' => '砻',
+'祕' => '秘',
'祘' => '算',
'祿' => '禄',
'禍' => '祸',
@@ -11183,13 +10840,20 @@ $zh2Hans = array(
'禱' => '祷',
'禿' => '秃',
'秈' => '籼',
+'秊' => '年',
+'秌' => '秋',
+'秖' => '只',
'稅' => '税',
'稈' => '秆',
+'稉' => '粳',
'稏' => '䅉',
'稜' => '棱',
'稟' => '禀',
+'稬' => '糯',
+'稭' => '秸',
'種' => '种',
'稱' => '称',
+'稾' => '稿',
'穀' => '谷',
'穌' => '稣',
'積' => '积',
@@ -11197,50 +10861,71 @@ $zh2Hans = array(
'穠' => '秾',
'穡' => '穑',
'穢' => '秽',
+'穤' => '糯',
+'穨' => '颓',
'穩' => '稳',
'穫' => '获',
'穭' => '稆',
+'穽' => '阱',
+'窓' => '窗',
'窩' => '窝',
'窪' => '洼',
'窮' => '穷',
'窯' => '窑',
+'窰' => '窑',
'窵' => '窎',
'窶' => '窭',
'窺' => '窥',
+'窻' => '窗',
'竄' => '窜',
'竅' => '窍',
'竇' => '窦',
+'竈' => '灶',
'竊' => '窃',
+'竚' => '伫',
+'竝' => '并',
+'竢' => '俟',
+'竪' => '竖',
'競' => '竞',
'筆' => '笔',
'筍' => '笋',
+'筞' => '策',
'筧' => '笕',
+'筩' => '筒',
+'筯' => '箸',
'筴' => '䇲',
'箇' => '个',
'箋' => '笺',
'箏' => '筝',
+'箒' => '帚',
+'箠' => '棰',
'節' => '节',
'範' => '范',
'築' => '筑',
'篋' => '箧',
'篔' => '筼',
'篘' => '𥬠',
+'篛' => '箬',
'篤' => '笃',
'篩' => '筛',
'篳' => '筚',
'簀' => '箦',
'簍' => '篓',
'簑' => '蓑',
+'簒' => '篡',
'簞' => '箪',
'簡' => '简',
'簣' => '篑',
'簫' => '箫',
+'簮' => '簪',
+'簷' => '檐',
'簹' => '筜',
'簽' => '签',
'簾' => '帘',
'籃' => '篮',
'籋' => '𥬞',
'籌' => '筹',
+'籐' => '藤',
'籔' => '䉤',
'籙' => '箓',
'籛' => '篯',
@@ -11253,7 +10938,10 @@ $zh2Hans = array(
'籬' => '篱',
'籮' => '箩',
'籲' => '吁',
+'粃' => '秕',
+'粧' => '妆',
'粵' => '粤',
+'糉' => '粽',
'糝' => '糁',
'糞' => '粪',
'糧' => '粮',
@@ -11286,6 +10974,7 @@ $zh2Hans = array(
'紜' => '纭',
'紝' => '纴',
'紡' => '纺',
+'紥' => '扎',
'紬' => '䌷',
'紮' => '扎',
'細' => '细',
@@ -11299,10 +10988,12 @@ $zh2Hans = array(
'紿' => '绐',
'絀' => '绌',
'終' => '终',
+'絃' => '弦',
'組' => '组',
'絅' => '䌹',
'絆' => '绊',
'絎' => '绗',
+'絏' => '绁',
'結' => '结',
'絕' => '绝',
'絛' => '绦',
@@ -11324,9 +11015,11 @@ $zh2Hans = array(
'綆' => '绠',
'綇' => '𦈋',
'綈' => '绨',
+'綉' => '绣',
'綌' => '绤',
'綏' => '绥',
'綐' => '䌼',
+'綑' => '捆',
'經' => '经',
'綜' => '综',
'綞' => '缍',
@@ -11354,17 +11047,22 @@ $zh2Hans = array(
'緊' => '紧',
'緋' => '绯',
'緍' => '𦈏',
+'緐' => '繁',
+'緑' => '绿',
'緒' => '绪',
'緓' => '绬',
+'緔' => '绱',
'緗' => '缃',
'緘' => '缄',
'緙' => '缂',
'線' => '线',
+'緜' => '绵',
'緝' => '缉',
'緞' => '缎',
'締' => '缔',
'緡' => '缗',
'緣' => '缘',
+'緥' => '褓',
'緦' => '缌',
'編' => '编',
'緩' => '缓',
@@ -11379,6 +11077,7 @@ $zh2Hans = array(
'緸' => '𦈑',
'緹' => '缇',
'緻' => '致',
+'緼' => '缊',
'縈' => '萦',
'縉' => '缙',
'縊' => '缢',
@@ -11393,6 +11092,7 @@ $zh2Hans = array(
'縞' => '缟',
'縟' => '缛',
'縣' => '县',
+'縧' => '绦',
'縫' => '缝',
'縬' => '𦈚',
'縭' => '缡',
@@ -11417,15 +11117,19 @@ $zh2Hans = array(
'繓' => '𦈛',
'織' => '织',
'繕' => '缮',
+'繖' => '伞',
+'繙' => '翻',
'繚' => '缭',
'繞' => '绕',
'繟' => '𦈎',
'繡' => '绣',
'繢' => '缋',
+'繦' => '襁',
'繩' => '绳',
'繪' => '绘',
'繫' => '系',
'繭' => '茧',
+'繮' => '缰',
'繯' => '缳',
'繰' => '缲',
'繳' => '缴',
@@ -11449,23 +11153,34 @@ $zh2Hans = array(
'纘' => '缵',
'纜' => '缆',
'缽' => '钵',
+'罇' => '樽',
'罈' => '坛',
+'罋' => '瓮',
'罌' => '罂',
'罎' => '坛',
'罰' => '罚',
'罵' => '骂',
'罷' => '罢',
+'罸' => '罚',
'羅' => '罗',
'羆' => '罴',
'羈' => '羁',
'羋' => '芈',
+'羗' => '羌',
+'羢' => '绒',
+'羣' => '群',
'羥' => '羟',
'羨' => '羡',
'義' => '义',
+'羶' => '膻',
+'翄' => '翅',
'習' => '习',
+'翫' => '玩',
'翬' => '翚',
+'翶' => '翱',
'翹' => '翘',
'翽' => '翙',
+'耡' => '锄',
'耬' => '耧',
'耮' => '耢',
'聖' => '圣',
@@ -11481,13 +11196,21 @@ $zh2Hans = array(
'聽' => '听',
'聾' => '聋',
'肅' => '肃',
+'肎' => '肯',
+'肐' => '胳',
+'肧' => '胚',
+'胷' => '胸',
+'脃' => '脆',
'脅' => '胁',
+'脇' => '胁',
'脈' => '脉',
+'脗' => '吻',
'脛' => '胫',
'脣' => '唇',
'脥' => '𣍰',
'脫' => '脱',
'脹' => '胀',
+'腁' => '胼',
'腎' => '肾',
'腖' => '胨',
'腡' => '脶',
@@ -11497,66 +11220,90 @@ $zh2Hans = array(
'腳' => '脚',
'腸' => '肠',
'膃' => '腽',
+'膓' => '肠',
'膕' => '腘',
'膚' => '肤',
+'膞' => '䏝',
'膠' => '胶',
'膢' => '𦝼',
'膩' => '腻',
'膽' => '胆',
'膾' => '脍',
'膿' => '脓',
+'臈' => '腊',
'臉' => '脸',
+'臋' => '臀',
'臍' => '脐',
'臏' => '膑',
+'臕' => '膘',
'臗' => '𣎑',
'臘' => '腊',
+'臙' => '胭',
'臚' => '胪',
+'臝' => '裸',
'臟' => '脏',
'臠' => '脔',
'臢' => '臜',
'臥' => '卧',
'臨' => '临',
+'臯' => '皋',
'臺' => '台',
'與' => '与',
'興' => '兴',
'舉' => '举',
'舊' => '旧',
+'舖' => '铺',
'舘' => '馆',
+'舩' => '船',
'艙' => '舱',
+'艢' => '樯',
+'艣' => '橹',
'艤' => '舣',
'艦' => '舰',
+'艪' => '橹',
'艫' => '舻',
'艱' => '艰',
'艷' => '艳',
+'芲' => '花',
'芻' => '刍',
'苧' => '苎',
+'茘' => '荔',
'茲' => '兹',
'荊' => '荆',
+'荳' => '豆',
'莊' => '庄',
'莖' => '茎',
'莢' => '荚',
'莧' => '苋',
'華' => '华',
-'菴' => '庵',
+'菸' => '烟',
'萇' => '苌',
'萊' => '莱',
'萬' => '万',
+'萲' => '萱',
'萴' => '荝',
'萵' => '莴',
'葉' => '叶',
'葒' => '荭',
+'葠' => '参',
'葤' => '荮',
'葦' => '苇',
+'葯' => '药',
'葷' => '荤',
+'蒓' => '莼',
'蒔' => '莳',
'蒞' => '莅',
'蒼' => '苍',
'蓀' => '荪',
+'蓆' => '席',
'蓋' => '盖',
+'蓡' => '参',
'蓮' => '莲',
'蓯' => '苁',
'蓴' => '莼',
'蓽' => '荜',
+'蔔' => '卜',
+'蔕' => '蒂',
'蔘' => '参',
'蔞' => '蒌',
'蔣' => '蒋',
@@ -11570,11 +11317,13 @@ $zh2Hans = array(
'蕓' => '芸',
'蕕' => '莸',
'蕘' => '荛',
+'蕚' => '萼',
'蕢' => '蒉',
'蕩' => '荡',
'蕪' => '芜',
'蕭' => '萧',
'蕷' => '蓣',
+'蕿' => '萱',
'薀' => '蕰',
'薈' => '荟',
'薊' => '蓟',
@@ -11593,17 +11342,24 @@ $zh2Hans = array(
'藝' => '艺',
'藥' => '药',
'藪' => '薮',
+'藭' => '䓖',
+'藴' => '蕴',
'藶' => '苈',
+'藷' => '薯',
'藹' => '蔼',
'藺' => '蔺',
+'藼' => '萱',
+'蘀' => '萚',
'蘄' => '蕲',
'蘆' => '芦',
'蘇' => '苏',
'蘊' => '蕴',
-'蘋' => '苹',
+'蘐' => '萱',
+'蘓' => '苏',
'蘚' => '藓',
'蘞' => '蔹',
'蘢' => '茏',
+'蘤' => '花',
'蘭' => '兰',
'蘺' => '蓠',
'蘿' => '萝',
@@ -11614,20 +11370,32 @@ $zh2Hans = array(
'號' => '号',
'虧' => '亏',
'虯' => '虬',
+'蚘' => '蛔',
+'蛕' => '蛔',
'蛺' => '蛱',
'蛻' => '蜕',
'蜆' => '蚬',
+'蜋' => '螂',
+'蜖' => '蛔',
+'蜨' => '蝶',
'蝕' => '蚀',
'蝟' => '猬',
'蝦' => '虾',
+'蝨' => '虱',
+'蝯' => '猿',
+'蝱' => '虻',
'蝸' => '蜗',
'螄' => '蛳',
+'螎' => '融',
'螞' => '蚂',
+'螡' => '蚊',
'螢' => '萤',
'螮' => '䗖',
'螻' => '蝼',
'螿' => '螀',
+'蟁' => '蚊',
'蟄' => '蛰',
+'蟇' => '蟆',
'蟈' => '蝈',
'蟎' => '螨',
'蟣' => '虮',
@@ -11640,37 +11408,53 @@ $zh2Hans = array(
'蠅' => '蝇',
'蠆' => '虿',
'蠍' => '蝎',
+'蠏' => '蟹',
'蠐' => '蛴',
'蠑' => '蝾',
+'蠒' => '茧',
+'蠔' => '蚝',
'蠟' => '蜡',
'蠣' => '蛎',
'蠨' => '蟏',
+'蠭' => '蜂',
'蠱' => '蛊',
'蠶' => '蚕',
'蠻' => '蛮',
+'衂' => '衄',
+'衆' => '众',
+'衇' => '脉',
'衊' => '蔑',
'術' => '术',
'衕' => '同',
+'衖' => '弄',
'衚' => '胡',
'衛' => '卫',
'衝' => '冲',
+'衞' => '卫',
+'衺' => '邪',
'袞' => '衮',
+'袟' => '帙',
+'袵' => '衽',
'裊' => '袅',
+'裌' => '袷',
'裏' => '里',
'補' => '补',
'裝' => '装',
+'裠' => '裙',
'裡' => '里',
'製' => '制',
'複' => '复',
'褌' => '裈',
'褘' => '袆',
+'褭' => '袅',
'褲' => '裤',
'褳' => '裢',
'褸' => '褛',
'褻' => '亵',
'襀' => '𫌀',
-'襆' => '幞',
+'襃' => '褒',
'襉' => '裥',
+'襍' => '杂',
'襏' => '袯',
'襖' => '袄',
'襝' => '裣',
@@ -11681,15 +11465,20 @@ $zh2Hans = array(
'襯' => '衬',
'襲' => '袭',
'襴' => '襕',
+'覇' => '霸',
+'覈' => '核',
+'覊' => '羁',
'見' => '见',
'覎' => '觃',
'規' => '规',
'覓' => '觅',
+'覔' => '觅',
'視' => '视',
'覘' => '觇',
'覡' => '觋',
'覥' => '觍',
'覦' => '觎',
+'覩' => '睹',
'親' => '亲',
'覬' => '觊',
'覯' => '觏',
@@ -11700,6 +11489,8 @@ $zh2Hans = array(
'覽' => '览',
'覿' => '觌',
'觀' => '观',
+'觔' => '斤',
+'觝' => '抵',
'觴' => '觞',
'觶' => '觯',
'觸' => '触',
@@ -11732,12 +11523,12 @@ $zh2Hans = array(
'訶' => '诃',
'診' => '诊',
'註' => '注',
+'証' => '证',
'詀' => '𧮪',
'詁' => '诂',
'詆' => '诋',
'詎' => '讵',
'詐' => '诈',
-'詑' => '𫍟',
'詒' => '诒',
'詔' => '诏',
'評' => '评',
@@ -11761,6 +11552,7 @@ $zh2Hans = array(
'該' => '该',
'詳' => '详',
'詵' => '诜',
+'詶' => '酬',
'詼' => '诙',
'詿' => '诖',
'誄' => '诔',
@@ -11772,6 +11564,7 @@ $zh2Hans = array(
'誑' => '诳',
'誒' => '诶',
'誕' => '诞',
+'誖' => '悖',
'誘' => '诱',
'誚' => '诮',
'語' => '语',
@@ -11783,6 +11576,7 @@ $zh2Hans = array(
'誦' => '诵',
'誨' => '诲',
'說' => '说',
+'説' => '说',
'誰' => '谁',
'課' => '课',
'誶' => '谇',
@@ -11805,13 +11599,14 @@ $zh2Hans = array(
'諜' => '谍',
'諝' => '谞',
'諞' => '谝',
+'諡' => '谥',
'諢' => '诨',
'諤' => '谔',
'諦' => '谛',
'諧' => '谐',
'諫' => '谏',
'諭' => '谕',
-'諮' => '咨',
+'諮' => '谘',
'諰' => '𫍰',
'諱' => '讳',
'諳' => '谙',
@@ -11827,6 +11622,7 @@ $zh2Hans = array(
'謄' => '誊',
'謅' => '诌',
'謊' => '谎',
+'謌' => '歌',
'謎' => '谜',
'謏' => '𫍲',
'謐' => '谧',
@@ -11838,21 +11634,28 @@ $zh2Hans = array(
'講' => '讲',
'謝' => '谢',
'謠' => '谣',
+'謡' => '谣',
'謨' => '谟',
'謫' => '谪',
'謬' => '谬',
+'謭' => '谫',
'謳' => '讴',
'謹' => '谨',
'謾' => '谩',
+'譁' => '哗',
+'譆' => '嘻',
'證' => '证',
'譊' => '𫍢',
+'譌' => '讹',
'譎' => '谲',
'譏' => '讥',
+'譔' => '撰',
'譖' => '谮',
'識' => '识',
'譙' => '谯',
'譚' => '谭',
'譜' => '谱',
+'譟' => '噪',
'譫' => '谵',
'譭' => '毁',
'譯' => '译',
@@ -11863,10 +11666,12 @@ $zh2Hans = array(
'譽' => '誉',
'譾' => '谫',
'讀' => '读',
+'讁' => '谪',
'變' => '变',
'讋' => '詟',
'讌' => '䜩',
'讎' => '仇',
+'讐' => '雠',
'讒' => '谗',
'讓' => '让',
'讕' => '谰',
@@ -11877,11 +11682,14 @@ $zh2Hans = array(
'豈' => '岂',
'豎' => '竖',
'豐' => '丰',
+'豓' => '艳',
'豔' => '艳',
'豬' => '猪',
'豶' => '豮',
+'貍' => '狸',
'貓' => '猫',
'貙' => '䝙',
+'貛' => '獾',
'貝' => '贝',
'貞' => '贞',
'貟' => '贠',
@@ -11915,6 +11723,7 @@ $zh2Hans = array(
'賅' => '赅',
'資' => '资',
'賈' => '贾',
+'賉' => '恤',
'賊' => '贼',
'賑' => '赈',
'賒' => '赊',
@@ -11922,6 +11731,7 @@ $zh2Hans = array(
'賕' => '赇',
'賙' => '赒',
'賚' => '赉',
+'賛' => '赞',
'賜' => '赐',
'賞' => '赏',
'賟' => '𧹖',
@@ -11933,11 +11743,13 @@ $zh2Hans = array(
'賦' => '赋',
'賧' => '赕',
'質' => '质',
+'賫' => '赍',
'賬' => '账',
'賭' => '赌',
'賰' => '䞐',
'賴' => '赖',
'賵' => '赗',
+'賷' => '赍',
'賺' => '赚',
'賻' => '赙',
'購' => '购',
@@ -11949,26 +11761,41 @@ $zh2Hans = array(
'贇' => '赟',
'贈' => '赠',
'贊' => '赞',
+'贋' => '赝',
'贍' => '赡',
'贏' => '赢',
'贐' => '赆',
+'贑' => '赣',
'贓' => '赃',
'贔' => '赑',
'贖' => '赎',
'贗' => '赝',
'贛' => '赣',
+'贜' => '赃',
'赬' => '赪',
+'趂' => '趁',
'趕' => '赶',
'趙' => '赵',
'趨' => '趋',
'趲' => '趱',
'跡' => '迹',
+'跥' => '跺',
+'跴' => '踩',
+'踁' => '胫',
'踐' => '践',
+'踫' => '碰',
+'踰' => '逾',
'踴' => '踊',
'蹌' => '跄',
+'蹏' => '蹄',
+'蹔' => '暂',
'蹕' => '跸',
+'蹟' => '迹',
+'蹠' => '跖',
'蹣' => '蹒',
'蹤' => '踪',
+'蹧' => '糟',
+'蹵' => '蹴',
'蹺' => '跷',
'蹻' => '𫏋',
'躂' => '跶',
@@ -11987,6 +11814,9 @@ $zh2Hans = array(
'躥' => '蹿',
'躦' => '躜',
'躪' => '躏',
+'躭' => '耽',
+'躳' => '躬',
+'躶' => '裸',
'軀' => '躯',
'軉' => '𨉗',
'車' => '车',
@@ -12022,6 +11852,7 @@ $zh2Hans = array(
'輔' => '辅',
'輕' => '轻',
'輗' => '𫐐',
+'輙' => '辄',
'輛' => '辆',
'輜' => '辎',
'輝' => '辉',
@@ -12032,11 +11863,13 @@ $zh2Hans = array(
'輩' => '辈',
'輪' => '轮',
'輬' => '辌',
+'輭' => '软',
'輮' => '𫐓',
'輯' => '辑',
'輳' => '辏',
'輸' => '输',
'輻' => '辐',
+'輼' => '辒',
'輾' => '辗',
'輿' => '舆',
'轀' => '辒',
@@ -12053,17 +11886,27 @@ $zh2Hans = array(
'轢' => '轹',
'轣' => '𫐆',
'轤' => '轳',
+'辠' => '罪',
+'辢' => '辣',
+'辤' => '辞',
'辦' => '办',
'辭' => '辞',
'辮' => '辫',
'辯' => '辩',
'農' => '农',
+'辳' => '农',
'迴' => '回',
+'迻' => '移',
+'逈' => '迥',
'逕' => '迳',
'這' => '这',
'連' => '连',
+'逥' => '回',
+'逩' => '奔',
+'逬' => '迸',
'週' => '周',
'進' => '进',
+'遉' => '侦',
'遊' => '游',
'運' => '运',
'過' => '过',
@@ -12075,7 +11918,9 @@ $zh2Hans = array(
'遠' => '远',
'遡' => '溯',
'適' => '适',
+'遯' => '遁',
'遲' => '迟',
+'遶' => '绕',
'遷' => '迁',
'選' => '选',
'遺' => '遗',
@@ -12102,6 +11947,11 @@ $zh2Hans = array(
'鄺' => '邝',
'酇' => '酂',
'酈' => '郦',
+'酖' => '鸩',
+'酧' => '酬',
+'醃' => '腌',
+'醆' => '盏',
+'醕' => '醇',
'醜' => '丑',
'醞' => '酝',
'醣' => '糖',
@@ -12109,12 +11959,13 @@ $zh2Hans = array(
'醬' => '酱',
'醯' => '酰',
'醱' => '酦',
+'醻' => '酬',
+'醼' => '宴',
'釀' => '酿',
'釁' => '衅',
'釃' => '酾',
'釅' => '酽',
'釋' => '释',
-'釐' => '厘',
'釒' => '钅',
'釓' => '钆',
'釔' => '钇',
@@ -12125,8 +11976,10 @@ $zh2Hans = array(
'針' => '针',
'釣' => '钓',
'釤' => '钐',
+'釦' => '扣',
'釧' => '钏',
'釩' => '钒',
+'釬' => '焊',
'釳' => '𨰿',
'釵' => '钗',
'釷' => '钍',
@@ -12137,11 +11990,13 @@ $zh2Hans = array(
'鈁' => '钫',
'鈃' => '钘',
'鈄' => '钭',
+'鈅' => '钥',
'鈇' => '𫓧',
'鈈' => '钚',
'鈉' => '钠',
'鈋' => '𨱂',
'鈍' => '钝',
+'鈎' => '钩',
'鈐' => '钤',
'鈑' => '钣',
'鈒' => '钑',
@@ -12169,6 +12024,7 @@ $zh2Hans = array(
'鉀' => '钾',
'鉁' => '𨱅',
'鉅' => '钜',
+'鉆' => '钻',
'鉈' => '铊',
'鉉' => '铉',
'鉋' => '铇',
@@ -12179,6 +12035,7 @@ $zh2Hans = array(
'鉚' => '铆',
'鉛' => '铅',
'鉞' => '钺',
+'鉢' => '钵',
'鉤' => '钩',
'鉦' => '钲',
'鉬' => '钼',
@@ -12209,6 +12066,7 @@ $zh2Hans = array(
'銫' => '铯',
'銬' => '铐',
'銱' => '铞',
+'銲' => '焊',
'銳' => '锐',
'銶' => '𨱇',
'銷' => '销',
@@ -12232,6 +12090,7 @@ $zh2Hans = array(
'鋨' => '锇',
'鋩' => '铓',
'鋪' => '铺',
+'鋭' => '锐',
'鋮' => '铖',
'鋯' => '锆',
'鋰' => '锂',
@@ -12263,10 +12122,12 @@ $zh2Hans = array(
'錫' => '锡',
'錮' => '锢',
'錯' => '错',
+'録' => '录',
'錳' => '锰',
'錶' => '表',
'錸' => '铼',
'鍀' => '锝',
+'鍁' => '锨',
'鍃' => '锪',
'鍄' => '𨱉',
'鍆' => '钔',
@@ -12283,13 +12144,15 @@ $zh2Hans = array(
'鍤' => '锸',
'鍥' => '锲',
'鍩' => '锘',
+'鍫' => '锹',
'鍬' => '锹',
'鍮' => '𨱎',
'鍰' => '锾',
+'鍳' => '鉴',
'鍵' => '键',
'鍶' => '锶',
'鍺' => '锗',
-'鍾' => '钟',
+'鍾' => '锺',
'鎂' => '镁',
'鎄' => '锿',
'鎇' => '镅',
@@ -12297,7 +12160,9 @@ $zh2Hans = array(
'鎌' => '镰',
'鎔' => '镕',
'鎖' => '锁',
+'鎗' => '枪',
'鎘' => '镉',
+'鎚' => '锤',
'鎛' => '镈',
'鎝' => '𨱏',
'鎡' => '镃',
@@ -12308,7 +12173,7 @@ $zh2Hans = array(
'鎩' => '铩',
'鎪' => '锼',
'鎬' => '镐',
-'鎭' => '鎮',
+'鎭' => '镇',
'鎮' => '镇',
'鎯' => '𨱍',
'鎰' => '镒',
@@ -12316,6 +12181,8 @@ $zh2Hans = array(
'鎳' => '镍',
'鎵' => '镓',
'鎷' => '𨰾',
+'鎸' => '镌',
+'鎻' => '锁',
'鎿' => '镎',
'鏃' => '镞',
'鏆' => '𨱌',
@@ -12356,6 +12223,7 @@ $zh2Hans = array(
'鐔' => '镡',
'鐘' => '钟',
'鐙' => '镫',
+'鐝' => '镢',
'鐠' => '镨',
'鐥' => '䦅',
'鐦' => '锎',
@@ -12378,14 +12246,18 @@ $zh2Hans = array(
'鑒' => '鉴',
'鑔' => '镲',
'鑕' => '锧',
+'鑚' => '钻',
+'鑛' => '矿',
'鑞' => '镴',
'鑠' => '铄',
'鑣' => '镳',
+'鑤' => '刨',
'鑥' => '镥',
'鑭' => '镧',
'鑰' => '钥',
'鑱' => '镵',
'鑲' => '镶',
+'鑵' => '罐',
'鑷' => '镊',
'鑹' => '镩',
'鑼' => '锣',
@@ -12393,7 +12265,7 @@ $zh2Hans = array(
'鑾' => '銮',
'鑿' => '凿',
'钁' => '镢',
-'镟' => '旋',
+'钂' => '镋',
'長' => '长',
'門' => '门',
'閂' => '闩',
@@ -12412,16 +12284,19 @@ $zh2Hans = array(
'間' => '间',
'閔' => '闵',
'閘' => '闸',
+'閙' => '闹',
'閡' => '阂',
'閣' => '阁',
-'閤' => '合',
+'閤' => '阁',
'閥' => '阀',
+'閧' => '哄',
'閨' => '闺',
'閩' => '闽',
'閫' => '阃',
'閬' => '阆',
'閭' => '闾',
'閱' => '阅',
+'閲' => '阅',
'閶' => '阊',
'閹' => '阉',
'閻' => '阎',
@@ -12431,6 +12306,7 @@ $zh2Hans = array(
'閿' => '阌',
'闃' => '阒',
'闆' => '板',
+'闇' => '暗',
'闈' => '闱',
'闊' => '阔',
'闋' => '阕',
@@ -12442,6 +12318,7 @@ $zh2Hans = array(
'闔' => '阖',
'闕' => '阙',
'闖' => '闯',
+'闚' => '窥',
'關' => '关',
'闞' => '阚',
'闠' => '阓',
@@ -12449,23 +12326,33 @@ $zh2Hans = array(
'闢' => '辟',
'闤' => '阛',
'闥' => '闼',
+'阨' => '厄',
+'阬' => '坑',
+'陗' => '峭',
'陘' => '陉',
+'陜' => '陕',
'陝' => '陕',
-'陞' => '升',
'陣' => '阵',
'陰' => '阴',
'陳' => '陈',
'陸' => '陆',
+'陻' => '堙',
'陽' => '阳',
+'陿' => '狭',
+'隂' => '阴',
+'隄' => '堤',
'隉' => '陧',
'隊' => '队',
'階' => '阶',
'隕' => '陨',
+'隖' => '坞',
'際' => '际',
+'隣' => '邻',
'隨' => '随',
'險' => '险',
'隱' => '隐',
'隴' => '陇',
+'隷' => '隶',
'隸' => '隶',
'隻' => '只',
'雋' => '隽',
@@ -12483,19 +12370,27 @@ $zh2Hans = array(
'霽' => '霁',
'靂' => '雳',
'靄' => '霭',
+'靆' => '叇',
'靈' => '灵',
+'靉' => '叆',
'靚' => '靓',
'靜' => '静',
'靦' => '腼',
'靨' => '靥',
+'靭' => '韧',
+'靱' => '韧',
'鞀' => '鼗',
+'鞌' => '鞍',
'鞏' => '巩',
'鞝' => '绱',
'鞦' => '秋',
+'鞵' => '鞋',
'鞽' => '鞒',
+'鞾' => '靴',
'韁' => '缰',
'韃' => '鞑',
'韆' => '千',
+'韈' => '袜',
'韉' => '鞯',
'韋' => '韦',
'韌' => '韧',
@@ -12503,8 +12398,9 @@ $zh2Hans = array(
'韓' => '韩',
'韙' => '韪',
'韜' => '韬',
-'韝' => '鞲',
'韞' => '韫',
+'韤' => '袜',
+'韮' => '韭',
'韻' => '韵',
'響' => '响',
'頁' => '页',
@@ -12525,6 +12421,7 @@ $zh2Hans = array(
'頗' => '颇',
'領' => '领',
'頜' => '颌',
+'頟' => '额',
'頡' => '颉',
'頤' => '颐',
'頦' => '颏',
@@ -12537,20 +12434,26 @@ $zh2Hans = array(
'頸' => '颈',
'頹' => '颓',
'頻' => '频',
+'頼' => '赖',
+'頽' => '颓',
'顃' => '𩖖',
'顆' => '颗',
+'顇' => '悴',
+'顋' => '腮',
'題' => '题',
'額' => '额',
'顎' => '颚',
'顏' => '颜',
'顒' => '颙',
'顓' => '颛',
+'顔' => '颜',
'願' => '愿',
'顙' => '颡',
'顛' => '颠',
'類' => '类',
'顢' => '颟',
'顥' => '颢',
+'顦' => '憔',
'顧' => '顾',
'顫' => '颤',
'顬' => '颥',
@@ -12574,13 +12477,16 @@ $zh2Hans = array(
'颼' => '飕',
'颾' => '𩙫',
'飀' => '飗',
+'飃' => '飘',
'飄' => '飘',
'飆' => '飙',
'飈' => '飚',
'飛' => '飞',
+'飜' => '翻',
'飠' => '饣',
'飢' => '饥',
'飣' => '饤',
+'飤' => '饲',
'飥' => '饦',
'飩' => '饨',
'飪' => '饪',
@@ -12594,9 +12500,11 @@ $zh2Hans = array(
'飽' => '饱',
'飾' => '饰',
'飿' => '饳',
+'餁' => '饪',
'餃' => '饺',
'餄' => '饸',
'餅' => '饼',
+'餈' => '糍',
'餉' => '饷',
'養' => '养',
'餌' => '饵',
@@ -12609,7 +12517,7 @@ $zh2Hans = array(
'餕' => '馂',
'餖' => '饾',
'餗' => '𫗧',
-'餘' => '余',
+'餘' => '馀',
'餚' => '肴',
'餛' => '馄',
'餜' => '馃',
@@ -12620,12 +12528,14 @@ $zh2Hans = array(
'餭' => '𫗮',
'餱' => '糇',
'餳' => '饧',
-'餵' => '喂',
'餶' => '馉',
'餷' => '馇',
'餸' => '𩠌',
+'餹' => '糖',
'餺' => '馎',
+'餻' => '糕',
'餼' => '饩',
+'餽' => '馈',
'餾' => '馏',
'餿' => '馊',
'饁' => '馌',
@@ -12636,10 +12546,13 @@ $zh2Hans = array(
'饊' => '馓',
'饋' => '馈',
'饌' => '馔',
+'饍' => '膳',
+'饑' => '饥',
'饒' => '饶',
'饗' => '飨',
'饘' => '𫗴',
'饜' => '餍',
+'饝' => '馍',
'饞' => '馋',
'饢' => '馕',
'馬' => '马',
@@ -12651,6 +12564,7 @@ $zh2Hans = array(
'馹' => '驲',
'駁' => '驳',
'駃' => '𫘝',
+'駈' => '驱',
'駎' => '𩧨',
'駐' => '驻',
'駑' => '驽',
@@ -12663,10 +12577,12 @@ $zh2Hans = array(
'駛' => '驶',
'駝' => '驼',
'駟' => '驷',
+'駡' => '骂',
'駢' => '骈',
'駧' => '𩧲',
'駩' => '𩧴',
'駭' => '骇',
+'駮' => '驳',
'駰' => '骃',
'駱' => '骆',
'駶' => '𩧺',
@@ -12681,6 +12597,7 @@ $zh2Hans = array(
'騍' => '骒',
'騎' => '骑',
'騏' => '骐',
+'騐' => '验',
'騔' => '𩨀',
'騖' => '骛',
'騙' => '骗',
@@ -12688,6 +12605,7 @@ $zh2Hans = array(
'騝' => '𩨃',
'騟' => '𩨈',
'騠' => '𫘨',
+'騣' => '鬃',
'騤' => '骙',
'騧' => '䯄',
'騪' => '𩨄',
@@ -12703,7 +12621,7 @@ $zh2Hans = array(
'驁' => '骜',
'驂' => '骖',
'驃' => '骠',
-'驄' => '骢',
+'驄' => '𩨂',
'驅' => '驱',
'驊' => '骅',
'驋' => '𩧯',
@@ -12712,6 +12630,7 @@ $zh2Hans = array(
'驏' => '骣',
'驕' => '骄',
'驗' => '验',
+'驘' => '骡',
'驚' => '惊',
'驛' => '驿',
'驟' => '骤',
@@ -12722,21 +12641,33 @@ $zh2Hans = array(
'驪' => '骊',
'驫' => '骉',
'骯' => '肮',
+'骽' => '腿',
+'骾' => '鲠',
+'髈' => '膀',
'髏' => '髅',
'髒' => '脏',
'體' => '体',
'髕' => '髌',
'髖' => '髋',
+'髣' => '仿',
+'髥' => '髯',
'髮' => '发',
+'髴' => '佛',
+'鬀' => '剃',
'鬆' => '松',
+'鬉' => '鬃',
'鬍' => '胡',
'鬚' => '须',
'鬢' => '鬓',
'鬥' => '斗',
+'鬦' => '斗',
'鬧' => '闹',
'鬨' => '哄',
'鬩' => '阋',
+'鬪' => '斗',
+'鬭' => '斗',
'鬮' => '阄',
+'鬰' => '郁',
'鬱' => '郁',
'鬹' => '鬶',
'魎' => '魉',
@@ -12757,12 +12688,14 @@ $zh2Hans = array(
'鮊' => '鲌',
'鮋' => '鲉',
'鮍' => '鲏',
+'鮎' => '鲇',
'鮐' => '鲐',
'鮑' => '鲍',
'鮒' => '鲋',
'鮓' => '鲊',
'鮚' => '鲒',
'鮜' => '鲘',
+'鮝' => '鲞',
'鮞' => '鲕',
'鮟' => '𩽾',
'鮣' => '䲟',
@@ -12797,6 +12730,7 @@ $zh2Hans = array(
'鯨' => '鲸',
'鯪' => '鲮',
'鯫' => '鲰',
+'鯰' => '鲶',
'鯱' => '𩾇',
'鯴' => '鲺',
'鯶' => '𩽼',
@@ -12812,8 +12746,10 @@ $zh2Hans = array(
'鰌' => '䲡',
'鰍' => '鳅',
'鰏' => '鲾',
+'鰐' => '鳄',
'鰒' => '鳆',
'鰓' => '鳃',
+'鰛' => '鳁',
'鰜' => '鳒',
'鰟' => '鳑',
'鰠' => '鳋',
@@ -12861,6 +12797,7 @@ $zh2Hans = array(
'鳥' => '鸟',
'鳧' => '凫',
'鳩' => '鸠',
+'鳬' => '凫',
'鳲' => '鸤',
'鳳' => '凤',
'鳴' => '鸣',
@@ -12871,6 +12808,7 @@ $zh2Hans = array(
'鴃' => '𫛞',
'鴆' => '鸩',
'鴇' => '鸨',
+'鴈' => '雁',
'鴉' => '鸦',
'鴒' => '鸰',
'鴕' => '鸵',
@@ -12900,6 +12838,7 @@ $zh2Hans = array(
'鵚' => '𪉍',
'鵜' => '鹈',
'鵝' => '鹅',
+'鵞' => '鹅',
'鵠' => '鹄',
'鵡' => '鹉',
'鵪' => '鹌',
@@ -12908,6 +12847,7 @@ $zh2Hans = array(
'鵯' => '鹎',
'鵰' => '雕',
'鵲' => '鹊',
+'鵶' => '鸦',
'鵷' => '鹓',
'鵾' => '鹍',
'鶄' => '䴖',
@@ -12933,8 +12873,10 @@ $zh2Hans = array(
'鶻' => '鹘',
'鶼' => '鹣',
'鶿' => '鹚',
+'鷀' => '鹚',
'鷁' => '鹢',
'鷂' => '鹞',
+'鷄' => '鸡',
'鷈' => '䴘',
'鷊' => '鹝',
'鷓' => '鹧',
@@ -12948,8 +12890,10 @@ $zh2Hans = array(
'鷨' => '𪉊',
'鷫' => '鹔',
'鷯' => '鹩',
+'鷰' => '燕',
'鷲' => '鹫',
'鷳' => '鹇',
+'鷴' => '鹇',
'鷸' => '鹬',
'鷹' => '鹰',
'鷺' => '鹭',
@@ -12959,6 +12903,7 @@ $zh2Hans = array(
'鸇' => '鹯',
'鸋' => '𫛢',
'鸌' => '鹱',
+'鸎' => '莺',
'鸏' => '鹲',
'鸕' => '鸬',
'鸘' => '鹴',
@@ -12969,6 +12914,7 @@ $zh2Hans = array(
'鹵' => '卤',
'鹹' => '咸',
'鹺' => '鹾',
+'鹻' => '碱',
'鹼' => '碱',
'鹽' => '盐',
'麗' => '丽',
@@ -12979,7 +12925,8 @@ $zh2Hans = array(
'麫' => '面',
'麯' => '曲',
'麲' => '𪎉',
-'麴' => '曲',
+'麳' => '𪎌',
+'麴' => '麹',
'麵' => '面',
'麼' => '么',
'麽' => '么',
@@ -12993,10 +12940,12 @@ $zh2Hans = array(
'黷' => '黩',
'黽' => '黾',
'黿' => '鼋',
+'鼃' => '蛙',
+'鼇' => '鳌',
+'鼈' => '鳖',
'鼉' => '鼍',
'鼕' => '冬',
'鼴' => '鼹',
-'齇' => '齄',
'齊' => '齐',
'齋' => '斋',
'齎' => '赍',
@@ -13012,6 +12961,8 @@ $zh2Hans = array(
'齡' => '龄',
'齣' => '出',
'齦' => '龈',
+'齧' => '啮',
+'齩' => '咬',
'齪' => '龊',
'齬' => '龉',
'齲' => '龋',
@@ -13026,28 +12977,47 @@ $zh2Hans = array(
'龜' => '龟',
'龭' => '𩨎',
'龯' => '𨱆',
+'𠇮' => '命',
+'𠌂' => '伞',
'𠌥' => '𠆿',
'𠏢' => '𠉗',
+'𠕂' => '再',
+'𠕅' => '再',
+'𠖇' => '冥',
'𠞆' => '𠛆',
+'𠞰' => '剿',
'𠠎' => '𠚳',
+'𠪾' => '历',
+'𠴟' => '咩',
+'𠻳' => '嗽',
'𡄔' => '𠴢',
'𡄣' => '𠵸',
'𡅏' => '𠲥',
+'𡐨' => '野',
'𡑭' => '𡋗',
'𡓾' => '𡋀',
+'𡚁' => '弊',
'𡞵' => '㛟',
'𡠹' => '㛿',
'𡢃' => '㛠',
+'𡨘' => '冤',
+'𡨥' => '寇',
+'𡬶' => '寻',
'𡮉' => '𡭜',
'𡮣' => '𡭬',
'𡻕' => '岁',
'𡾱' => '㟜',
'𢣚' => '𢘝',
'𢣭' => '𢘞',
+'𢬸' => '括',
+'𢭏' => '捣',
+'𢮥' => '操',
'𢶫' => '𢫞',
+'𢷬' => '捣',
'𢷮' => '𢫊',
'𢹿' => '𢬦',
'𣙎' => '㭣',
+'𣙜' => '榷',
'𣝕' => '𣘷',
'𣞻' => '𣘓',
'𣠲' => '𣑶',
@@ -13055,16 +13025,25 @@ $zh2Hans = array(
'𣾷' => '㳢',
'𣿉' => '𣶫',
'𤁣' => '𣺽',
+'𤋮' => '熙',
'𤒎' => '𤊀',
+'𤨏' => '琐',
'𤪺' => '㻘',
'𤫩' => '㻏',
+'𤰜' => '亩',
+'𤱈' => '亩',
+'𤱊' => '留',
'𤳸' => '𤳄',
'𤸫' => '𤶧',
+'𤺥' => '瘩',
+'𥄨' => '瞅',
'𥌃' => '𥅘',
'𥕥' => '𥐰',
'𥖅' => '𥐯',
'𥢢' => '䅪',
+'𥦗' => '窗',
'𥨐' => '𥧂',
+'𥲻' => '纂',
'𥵃' => '𥱔',
'𥵊' => '𥭉',
'𥸠' => '𥮋',
@@ -13072,10 +13051,18 @@ $zh2Hans = array(
'𥽖' => '𥺇',
'𥿊' => '𦈈',
'𦂅' => '𦈒',
+'𦂳' => '紧',
+'𦃂' => '紧',
'𦃄' => '𦈗',
+'𦉆' => '碴',
+'𦊱' => '挂',
+'𦍑' => '羌',
+'𦕈' => '眇',
'𦢈' => '𣍨',
'𦣎' => '𦟗',
+'𦪙' => '䑽',
'𦪽' => '𦨩',
+'𦵏' => '葬',
'𧔥' => '𧒭',
'𧜗' => '䘞',
'𧜵' => '䙊',
@@ -13117,10 +13104,14 @@ $zh2Hans = array(
'𨶲' => '𨸋',
'𨷲' => '𨸎',
'𨽏' => '𨸘',
+'𨽻' => '隶',
'𩎢' => '𩏾',
'𩏪' => '𩏽',
+'𩓐' => '脖',
'𩓣' => '𩖕',
'𩗀' => '𩙦',
+'𩗗' => '飓',
+'𩗡' => '𩙧',
'𩘀' => '𩙩',
'𩘝' => '𩙭',
'𩘹' => '𩙨',
@@ -13170,6 +13161,7 @@ $zh2Hans = array(
'𩷰' => '𩾄',
'𩸃' => '𩾅',
'𩸦' => '𩾆',
+'𩽇' => '𩾎',
'𩿪' => '𪉄',
'𪀦' => '𪉅',
'𪀾' => '𪉋',
@@ -13181,68 +13173,26 @@ $zh2Hans = array(
'𪄆' => '𪉔',
'𪄕' => '𪉒',
'𪇳' => '𪉕',
+'𪈼' => '𪉓',
'𪋿' => '𪎍',
'𪔵' => '𪔭',
'𪘀' => '𪚏',
'𪘯' => '𪚐',
+'『' => '‘',
+'』' => '’',
+'「' => '“',
+'「' => '“',
+'」' => '”',
+'」' => '”',
+'。陞' => '。升',
'《易乾' => '《易乾',
-'不著痕跡' => '不着痕迹',
-'不著邊際' => '不着边际',
-'與著' => '与着',
-'與著書' => '与著书',
-'與著作' => '与著作',
-'與著名' => '与著名',
-'與著錄' => '与著录',
-'與著稱' => '与著称',
-'與著者' => '与著者',
-'與著述' => '与著述',
-'丑著' => '丑着',
-'丑著书' => '丑著书',
-'丑著書' => '丑著书',
-'丑著作' => '丑著作',
-'丑著名' => '丑著名',
-'丑著录' => '丑著录',
-'丑著錄' => '丑著录',
-'丑著稱' => '丑著称',
-'丑著称' => '丑著称',
-'丑著者' => '丑著者',
-'丑著述' => '丑著述',
+'一釐' => '一厘',
+'一口鍾' => '一口钟',
+'一鍾' => '一钟',
+'上昇' => '上升',
'專著' => '专著',
-'臨著' => '临着',
-'臨著書' => '临著书',
-'臨著作' => '临著作',
-'臨著名' => '临著名',
-'臨著錄' => '临著录',
-'臨著稱' => '临著称',
-'臨著者' => '临著者',
-'臨著述' => '临著述',
-'麗著' => '丽着',
-'麗著書' => '丽著书',
-'麗著作' => '丽著作',
-'麗著名' => '丽著名',
-'麗著錄' => '丽著录',
-'麗著稱' => '丽著称',
-'麗著者' => '丽著者',
-'麗著述' => '丽著述',
-'樂著' => '乐着',
-'樂著書' => '乐著书',
-'樂著作' => '乐著作',
-'樂著名' => '乐著名',
-'樂著錄' => '乐著录',
-'樂著稱' => '乐著称',
-'樂著者' => '乐著者',
-'樂著述' => '乐著述',
-'乘著' => '乘着',
-'乘著书' => '乘著书',
-'乘著書' => '乘著书',
-'乘著作' => '乘著作',
-'乘著名' => '乘著名',
-'乘著录' => '乘著录',
-'乘著錄' => '乘著录',
-'乘著稱' => '乘著称',
-'乘著称' => '乘著称',
-'乘著者' => '乘著者',
-'乘著述' => '乘著述',
+'世界鍾' => '世界钟',
+'喪鍾' => '丧钟',
'乾一坛' => '乾一坛',
'乾一壇' => '乾一坛',
'乾一组' => '乾一组',
@@ -13253,8 +13203,8 @@ $zh2Hans = array(
'乾九' => '乾九',
'乾乾' => '乾乾',
'乾亨' => '乾亨',
-'乾儀' => '乾仪',
'乾仪' => '乾仪',
+'乾儀' => '乾仪',
'乾位' => '乾位',
'乾健' => '乾健',
'乾健也' => '乾健也',
@@ -13262,12 +13212,12 @@ $zh2Hans = array(
'乾光' => '乾光',
'乾兴' => '乾兴',
'乾興' => '乾兴',
-'乾冈' => '乾冈',
'乾岡' => '乾冈',
-'乾劉' => '乾刘',
+'乾冈' => '乾冈',
'乾刘' => '乾刘',
-'乾剛' => '乾刚',
+'乾劉' => '乾刘',
'乾刚' => '乾刚',
+'乾剛' => '乾刚',
'乾務' => '乾务',
'乾务' => '乾务',
'乾化' => '乾化',
@@ -13289,16 +13239,16 @@ $zh2Hans = array(
'乾天也' => '乾天也',
'乾始' => '乾始',
'乾姓' => '乾姓',
-'乾寧' => '乾宁',
'乾宁' => '乾宁',
+'乾寧' => '乾宁',
'乾宅' => '乾宅',
'乾宇' => '乾宇',
'乾安' => '乾安',
'乾定' => '乾定',
'乾封' => '乾封',
'乾居' => '乾居',
-'乾崗' => '乾岗',
'乾岗' => '乾岗',
+'乾崗' => '乾岗',
'乾巛' => '乾巛',
'乾州' => '乾州',
'乾式' => '乾式',
@@ -13309,46 +13259,47 @@ $zh2Hans = array(
'乾心' => '乾心',
'乾忠' => '乾忠',
'乾文' => '乾文',
-'乾斷' => '乾断',
'乾断' => '乾断',
+'乾斷' => '乾断',
'乾方' => '乾方',
'乾施' => '乾施',
'乾旦' => '乾旦',
'乾明' => '乾明',
'乾昧' => '乾昧',
-'乾晖' => '乾晖',
'乾暉' => '乾晖',
+'乾晖' => '乾晖',
'乾景' => '乾景',
'乾晷' => '乾晷',
'乾曜' => '乾曜',
-'乾构' => '乾构',
'乾構' => '乾构',
-'乾樞' => '乾枢',
+'乾构' => '乾构',
'乾枢' => '乾枢',
-'乾栋' => '乾栋',
+'乾樞' => '乾枢',
'乾棟' => '乾栋',
+'乾栋' => '乾栋',
'乾步' => '乾步',
'乾氏' => '乾氏',
'乾沓和' => '乾沓和',
'乾沓婆' => '乾沓婆',
'乾泉' => '乾泉',
'乾淳' => '乾淳',
-'乾清宮' => '乾清宫',
-'乾清宫' => '乾清宫',
+'乾清' => '乾清',
'乾渥' => '乾渥',
-'乾靈' => '乾灵',
+'乾潭' => '乾潭',
'乾灵' => '乾灵',
+'乾靈' => '乾灵',
'乾男' => '乾男',
'乾皋' => '乾皋',
'乾盛世' => '乾盛世',
'乾矢' => '乾矢',
'乾祐' => '乾祐',
+'乾神' => '乾神',
'乾穹' => '乾穹',
'乾竇' => '乾窦',
'乾窦' => '乾窦',
'乾竺' => '乾竺',
-'乾篤' => '乾笃',
'乾笃' => '乾笃',
+'乾篤' => '乾笃',
'乾符' => '乾符',
'乾策' => '乾策',
'乾精' => '乾精',
@@ -13358,192 +13309,72 @@ $zh2Hans = array(
'乾纲' => '乾纲',
'乾纽' => '乾纽',
'乾紐' => '乾纽',
-'乾絡' => '乾络',
'乾络' => '乾络',
+'乾絡' => '乾络',
'乾統' => '乾统',
'乾统' => '乾统',
-'乾維' => '乾维',
'乾维' => '乾维',
-'乾羅' => '乾罗',
+'乾維' => '乾维',
'乾罗' => '乾罗',
+'乾羅' => '乾罗',
'乾花' => '乾花',
-'乾蔭' => '乾荫',
'乾荫' => '乾荫',
+'乾蔭' => '乾荫',
'乾行' => '乾行',
'乾衡' => '乾衡',
+'乾西' => '乾西',
'乾覆' => '乾覆',
'乾象' => '乾象',
'乾象歷' => '乾象历',
'乾象历' => '乾象历',
-'乾贞' => '乾贞',
'乾貞' => '乾贞',
+'乾贞' => '乾贞',
+'乾贵士' => '乾贵士',
+'乾貴士' => '乾贵士',
'乾貺' => '乾贶',
'乾贶' => '乾贶',
-'乾车' => '乾车',
'乾車' => '乾车',
-'乾軸' => '乾轴',
+'乾车' => '乾车',
'乾轴' => '乾轴',
+'乾軸' => '乾轴',
'乾通' => '乾通',
'乾造' => '乾造',
'乾道' => '乾道',
-'乾鑒' => '乾鉴',
'乾鉴' => '乾鉴',
-'乾钧' => '乾钧',
+'乾鑒' => '乾鉴',
'乾鈞' => '乾钧',
-'乾闼' => '乾闼',
+'乾钧' => '乾钧',
'乾闥' => '乾闼',
+'乾闼' => '乾闼',
'乾陀' => '乾陀',
'乾陵' => '乾陵',
'乾隆' => '乾隆',
'乾音' => '乾音',
-'乾顾' => '乾顾',
'乾顧' => '乾顾',
-'乾风' => '乾风',
+'乾顾' => '乾顾',
'乾風' => '乾风',
+'乾风' => '乾风',
'乾首' => '乾首',
-'乾馬' => '乾马',
'乾马' => '乾马',
+'乾馬' => '乾马',
'乾鵠' => '乾鹄',
'乾鹄' => '乾鹄',
'乾鵲' => '乾鹊',
'乾鹊' => '乾鹊',
-'乾龍' => '乾龙',
'乾龙' => '乾龙',
+'乾龍' => '乾龙',
'乾,健也' => '乾,健也',
'乾,天也' => '乾,天也',
-'爭著' => '争着',
-'爭著書' => '争著书',
-'爭著作' => '争著作',
-'爭著名' => '争著名',
-'爭著錄' => '争著录',
-'爭著稱' => '争著称',
-'爭著者' => '争著者',
-'爭著述' => '争著述',
'五箇山' => '五箇山',
-'亮著' => '亮着',
-'亮著书' => '亮著书',
-'亮著書' => '亮著书',
-'亮著作' => '亮著作',
-'亮著名' => '亮著名',
-'亮著錄' => '亮著录',
-'亮著录' => '亮著录',
-'亮著称' => '亮著称',
-'亮著稱' => '亮著称',
-'亮著者' => '亮著者',
-'亮著述' => '亮著述',
-'仗著' => '仗着',
-'仗著书' => '仗著书',
-'仗著書' => '仗著书',
-'仗著作' => '仗著作',
-'仗著名' => '仗著名',
-'仗著录' => '仗著录',
-'仗著錄' => '仗著录',
-'仗著稱' => '仗著称',
-'仗著称' => '仗著称',
-'仗著者' => '仗著者',
-'仗著述' => '仗著述',
-'代表著' => '代表着',
-'代表著書' => '代表著书',
-'代表著书' => '代表著书',
-'代表著作' => '代表著作',
-'代表著名' => '代表著名',
-'代表著錄' => '代表著录',
-'代表著录' => '代表著录',
-'代表著称' => '代表著称',
-'代表著稱' => '代表著称',
-'代表著者' => '代表著者',
-'代表著述' => '代表著述',
+'仇讎' => '仇雠',
'以微知著' => '以微知著',
+'以莛叩鍾' => '以莛叩钟',
'仰屋著書' => '仰屋著书',
'彷彿' => '仿佛',
'夥計' => '伙计',
-'傳著' => '传着',
-'傳著書' => '传著书',
-'傳著作' => '传著作',
-'傳著名' => '传著名',
-'傳著錄' => '传著录',
-'傳著稱' => '传著称',
-'傳著者' => '传著者',
-'傳著述' => '传著述',
-'伴著' => '伴着',
-'伴著书' => '伴著书',
-'伴著書' => '伴著书',
-'伴著作' => '伴著作',
-'伴著名' => '伴著名',
-'伴著录' => '伴著录',
-'伴著錄' => '伴著录',
-'伴著稱' => '伴著称',
-'伴著称' => '伴著称',
-'伴著者' => '伴著者',
-'伴著述' => '伴著述',
-'低著' => '低着',
-'低著書' => '低著书',
-'低著书' => '低著书',
-'低著作' => '低著作',
-'低著名' => '低著名',
-'低著录' => '低著录',
-'低著錄' => '低著录',
-'低著稱' => '低著称',
-'低著称' => '低著称',
-'低著者' => '低著者',
-'低著述' => '低著述',
-'住著' => '住着',
-'住著書' => '住著书',
-'住著书' => '住著书',
-'住著作' => '住著作',
-'住著名' => '住著名',
-'住著錄' => '住著录',
-'住著录' => '住著录',
-'住著称' => '住著称',
-'住著稱' => '住著称',
-'住著者' => '住著者',
-'住著述' => '住著述',
'佛頭著糞' => '佛头著粪',
-'侏儸紀' => '侏罗纪',
-'側著' => '侧着',
-'側著書' => '侧著书',
-'側著作' => '侧著作',
-'側著名' => '侧著名',
-'側著錄' => '侧著录',
-'側著稱' => '侧著称',
-'側著者' => '侧著者',
-'側著述' => '侧著述',
-'保護著' => '保护着',
-'保障著' => '保障着',
-'保障著书' => '保障著书',
-'保障著書' => '保障著书',
-'保障著作' => '保障著作',
-'保障著名' => '保障著名',
-'保障著錄' => '保障著录',
-'保障著录' => '保障著录',
-'保障著稱' => '保障著称',
-'保障著称' => '保障著称',
-'保障著者' => '保障著者',
-'保障著述' => '保障著述',
-'信著' => '信着',
-'信著书' => '信著书',
-'信著書' => '信著书',
-'信著作' => '信著作',
-'信著名' => '信著名',
-'信著录' => '信著录',
-'信著錄' => '信著录',
-'信著称' => '信著称',
-'信著稱' => '信著称',
-'信著者' => '信著者',
-'信著述' => '信著述',
-'修鍊' => '修炼',
+'偵蒐' => '侦搜',
'候覆' => '候复',
-'候著' => '候着',
-'候著書' => '候著书',
-'候著书' => '候著书',
-'候著作' => '候著作',
-'候著名' => '候著名',
-'候著录' => '候著录',
-'候著錄' => '候著录',
-'候著稱' => '候著称',
-'候著称' => '候著称',
-'候著者' => '候著者',
-'候著述' => '候著述',
'藉助' => '借助',
'藉口' => '借口',
'藉手' => '借手',
@@ -13551,1082 +13382,183 @@ $zh2Hans = array(
'藉機' => '借机',
'藉此' => '借此',
'藉由' => '借由',
-'借著' => '借着',
-'藉着' => '借着',
-'藉著' => '借着',
'藉端' => '借端',
-'借著書' => '借著书',
-'借著书' => '借著书',
-'借著作' => '借著作',
-'借著名' => '借著名',
-'借著录' => '借著录',
-'借著錄' => '借著录',
-'借著称' => '借著称',
-'借著稱' => '借著称',
-'借著者' => '借著者',
-'借著述' => '借著述',
'藉詞' => '借词',
-'做著' => '做着',
-'做著書' => '做著书',
-'做著书' => '做著书',
-'做著作' => '做著作',
-'做著名' => '做著名',
-'做著錄' => '做著录',
-'做著录' => '做著录',
-'做著稱' => '做著称',
-'做著称' => '做著称',
-'做著者' => '做著者',
-'做著述' => '做著述',
-'偷著' => '偷着',
-'偷著書' => '偷著书',
-'偷著书' => '偷著书',
-'偷著作' => '偷著作',
-'偷著名' => '偷著名',
-'偷著錄' => '偷著录',
-'偷著录' => '偷著录',
-'偷著稱' => '偷著称',
-'偷著称' => '偷著称',
-'偷著者' => '偷著者',
-'偷著述' => '偷著述',
-'傢俬' => '傢俬',
-'僧伽吒' => '僧伽吒',
-'光著' => '光着',
-'光著書' => '光著书',
-'光著书' => '光著书',
-'光著作' => '光著作',
-'光著名' => '光著名',
-'光著錄' => '光著录',
-'光著录' => '光著录',
-'光著稱' => '光著称',
-'光著称' => '光著称',
-'光著者' => '光著者',
-'光著述' => '光著述',
-'關著' => '关着',
-'關著書' => '关著书',
-'關著作' => '关著作',
-'關著名' => '关著名',
-'關著錄' => '关著录',
-'關著稱' => '关著称',
-'關著者' => '关著者',
-'關著述' => '关著述',
-'冀著' => '冀着',
-'冀著書' => '冀著书',
-'冀著书' => '冀著书',
-'冀著作' => '冀著作',
-'冀著名' => '冀著名',
-'冀著錄' => '冀著录',
-'冀著录' => '冀著录',
-'冀著稱' => '冀著称',
-'冀著称' => '冀著称',
-'冀著者' => '冀著者',
-'冀著述' => '冀著述',
-'冒著' => '冒着',
-'冒著书' => '冒著书',
-'冒著書' => '冒著书',
-'冒著作' => '冒著作',
-'冒著名' => '冒著名',
-'冒著录' => '冒著录',
-'冒著錄' => '冒著录',
-'冒著稱' => '冒著称',
-'冒著称' => '冒著称',
-'冒著者' => '冒著者',
-'冒著述' => '冒著述',
-'寫著' => '写着',
-'寫著書' => '写著书',
-'寫著作' => '写著作',
-'寫著名' => '写著名',
-'寫著錄' => '写著录',
-'寫著稱' => '写著称',
-'寫著者' => '写著者',
-'寫著述' => '写著述',
-'涼著' => '凉着',
-'涼著書' => '凉著书',
-'涼著作' => '凉著作',
-'涼著名' => '凉著名',
-'涼著錄' => '凉著录',
-'涼著稱' => '凉著称',
-'涼著者' => '凉著者',
-'涼著述' => '凉著述',
+'先名後姓' => '先名后姓',
+'六么' => '六幺',
+'蘭質薰心' => '兰质薰心',
+'內聯陞' => '内联升',
'憑藉' => '凭借',
-'制著' => '制着',
-'制著书' => '制著书',
-'制著書' => '制著书',
-'制著作' => '制著作',
-'制著名' => '制著名',
-'制著錄' => '制著录',
-'制著录' => '制著录',
-'制著称' => '制著称',
-'制著稱' => '制著称',
-'制著者' => '制著者',
-'制著述' => '制著述',
-'刻著' => '刻着',
-'刻著書' => '刻著书',
-'刻著书' => '刻著书',
-'刻著作' => '刻著作',
-'刻著名' => '刻著名',
-'刻著录' => '刻著录',
-'刻著錄' => '刻著录',
-'刻著稱' => '刻著称',
-'刻著称' => '刻著称',
-'刻著者' => '刻著者',
-'刻著述' => '刻著述',
-'辦著' => '办着',
-'辦著書' => '办著书',
-'辦著作' => '办著作',
-'辦著名' => '办著名',
-'辦著錄' => '办著录',
-'辦著稱' => '办著称',
-'辦著者' => '办著者',
-'辦著述' => '办著述',
-'動著' => '动着',
-'動著書' => '动著书',
-'動著作' => '动著作',
-'動著名' => '动著名',
-'動著錄' => '动著录',
-'動著稱' => '动著称',
-'動著者' => '动著者',
-'動著述' => '动著述',
-'努力著' => '努力着',
-'努力著書' => '努力著书',
-'努力著书' => '努力著书',
-'努力著作' => '努力著作',
-'努力著名' => '努力著名',
-'努力著錄' => '努力著录',
-'努力著录' => '努力著录',
-'努力著称' => '努力著称',
-'努力著稱' => '努力著称',
-'努力著者' => '努力著者',
-'努力著述' => '努力著述',
-'努著' => '努着',
-'努著書' => '努著书',
-'努著书' => '努著书',
-'努著作' => '努著作',
-'努著名' => '努著名',
-'努著錄' => '努著录',
-'努著录' => '努著录',
-'努著称' => '努著称',
-'努著稱' => '努著称',
-'努著者' => '努著者',
-'努著述' => '努著述',
+'分鍾' => '分钟',
+'初昇' => '初升',
+'利欲薰心' => '利欲薰心',
+'刻鍾' => '刻钟',
+'剋了' => '剋了',
+'剋架' => '剋架',
+'剖釐' => '剖厘',
+'千鍾' => '千钟',
+'陞為' => '升为',
+'陞了' => '升了',
+'昇仙' => '升仙',
+'陞任' => '升任',
+'昇華' => '升华',
+'昇天' => '升天',
+'陞官' => '升官',
+'昇平' => '升平',
+'昇汞' => '升汞',
+'陞用' => '升用',
+'陞補' => '升补',
+'陞遷' => '升迁',
+'昇降' => '升降',
'卓著' => '卓著',
-'印著' => '印着',
-'印著书' => '印著书',
-'印著書' => '印著书',
-'印著作' => '印著作',
-'印著名' => '印著名',
-'印著录' => '印著录',
-'印著錄' => '印著录',
-'印著称' => '印著称',
-'印著稱' => '印著称',
-'印著者' => '印著者',
-'印著述' => '印著述',
+'博和託' => '博和讬',
'卷舌' => '卷舌',
-'壓著' => '压着',
-'壓著書' => '压著书',
-'壓著作' => '压著作',
-'壓著名' => '压著名',
-'壓著錄' => '压著录',
-'壓著稱' => '压著称',
-'壓著者' => '压著者',
-'壓著述' => '压著述',
+'歷陞' => '历升',
+'釐改' => '厘改',
+'釐整' => '厘整',
+'釐正' => '厘正',
+'釐毫' => '厘毫',
+'釐清' => '厘清',
+'釐訂' => '厘订',
+'釐革' => '厘革',
+'原子鍾' => '原子钟',
'原著' => '原著',
-'去著' => '去着',
-'去著书' => '去著书',
-'去著書' => '去著书',
-'去著作' => '去著作',
-'去著名' => '去著名',
-'去著录' => '去著录',
-'去著錄' => '去著录',
-'去著稱' => '去著称',
-'去著称' => '去著称',
-'去著者' => '去著者',
-'去著述' => '去著述',
+'又陞' => '又升',
'反反覆覆' => '反反复复',
'反覆' => '反复',
-'受著' => '受着',
-'受著書' => '受著书',
-'受著书' => '受著书',
-'受著作' => '受著作',
-'受著名' => '受著名',
-'受著錄' => '受著录',
-'受著录' => '受著录',
-'受著稱' => '受著称',
-'受著称' => '受著称',
-'受著者' => '受著者',
-'受著述' => '受著述',
-'變著' => '变着',
-'變著書' => '变著书',
-'變著作' => '变著作',
-'變著名' => '变著名',
-'變著錄' => '变著录',
-'變著稱' => '变著称',
-'變著者' => '变著者',
-'變著述' => '变著述',
-'叫著' => '叫着',
-'叫著书' => '叫著书',
-'叫著書' => '叫著书',
-'叫著作' => '叫著作',
-'叫著名' => '叫著名',
-'叫著录' => '叫著录',
-'叫著錄' => '叫著录',
-'叫著称' => '叫著称',
-'叫著稱' => '叫著称',
-'叫著者' => '叫著者',
-'叫著述' => '叫著述',
+'古鍾' => '古钟',
'可穿著' => '可穿著',
-'叱吒' => '叱吒',
-'吃不著' => '吃不着',
-'吃得著' => '吃得着',
-'吃著' => '吃着',
+'台鍾' => '台钟',
'吃衣著飯' => '吃衣著饭',
'合著' => '合著',
+'同陞和' => '同升和',
'名著' => '名著',
-'向著' => '向着',
-'向著書' => '向著书',
-'向著书' => '向著书',
-'向著作' => '向著作',
-'向著名' => '向著名',
-'向著錄' => '向著录',
-'向著录' => '向著录',
-'向著称' => '向著称',
-'向著稱' => '向著称',
-'向著者' => '向著者',
-'向著述' => '向著述',
-'含著' => '含着',
-'含著書' => '含著书',
-'含著书' => '含著书',
-'含著作' => '含著作',
-'含著名' => '含著名',
-'含著錄' => '含著录',
-'含著录' => '含著录',
-'含著称' => '含著称',
-'含著稱' => '含著称',
-'含著者' => '含著者',
-'含著述' => '含著述',
-'聽不著' => '听不着',
-'聽得著' => '听得着',
-'聽著' => '听着',
-'聽著書' => '听著书',
-'聽著作' => '听著作',
-'聽著名' => '听著名',
-'聽著錄' => '听著录',
-'聽著稱' => '听著称',
-'聽著者' => '听著者',
-'聽著述' => '听著述',
-'吴其濬' => '吴其濬',
-'吳其濬' => '吴其濬',
-'吹著' => '吹着',
-'吹著書' => '吹著书',
-'吹著书' => '吹著书',
-'吹著作' => '吹著作',
-'吹著名' => '吹著名',
-'吹著录' => '吹著录',
-'吹著錄' => '吹著录',
-'吹著稱' => '吹著称',
-'吹著称' => '吹著称',
-'吹著者' => '吹著者',
-'吹著述' => '吹著述',
+'吳克羣' => '吴克羣',
'周易乾' => '周易乾',
-'味著' => '味着',
-'味著书' => '味著书',
-'味著書' => '味著书',
-'味著作' => '味著作',
-'味著名' => '味著名',
-'味著录' => '味著录',
-'味著錄' => '味著录',
-'味著称' => '味著称',
-'味著稱' => '味著称',
-'味著者' => '味著者',
-'味著述' => '味著述',
-'呼幺喝六' => '呼幺喝六',
-'響著' => '响着',
-'響著書' => '响著书',
-'響著作' => '响著作',
-'響著名' => '响著名',
-'響著錄' => '响著录',
-'響著稱' => '响著称',
-'響著者' => '响著者',
-'響著述' => '响著述',
-'哪吒' => '哪吒',
-'哭著' => '哭着',
-'哭著書' => '哭著书',
-'哭著书' => '哭著书',
-'哭著作' => '哭著作',
-'哭著名' => '哭著名',
-'哭著錄' => '哭著录',
-'哭著录' => '哭著录',
-'哭著稱' => '哭著称',
-'哭著称' => '哭著称',
-'哭著者' => '哭著者',
-'哭著述' => '哭著述',
-'唱著' => '唱着',
-'唱著书' => '唱著书',
-'唱著書' => '唱著书',
-'唱著作' => '唱著作',
-'唱著名' => '唱著名',
-'唱著录' => '唱著录',
-'唱著錄' => '唱著录',
-'唱著称' => '唱著称',
-'唱著稱' => '唱著称',
-'唱著者' => '唱著者',
-'唱著述' => '唱著述',
-'喝著' => '喝着',
-'喝著书' => '喝著书',
-'喝著書' => '喝著书',
-'喝著作' => '喝著作',
-'喝著名' => '喝著名',
-'喝著录' => '喝著录',
-'喝著錄' => '喝著录',
-'喝著稱' => '喝著称',
-'喝著称' => '喝著称',
-'喝著者' => '喝著者',
-'喝著述' => '喝著述',
-'嗅不著' => '嗅不着',
-'嗅得著' => '嗅得着',
-'嗅著' => '嗅着',
-'嚷著' => '嚷着',
-'嚷著書' => '嚷著书',
-'嚷著书' => '嚷著书',
-'嚷著作' => '嚷著作',
-'嚷著名' => '嚷著名',
-'嚷著錄' => '嚷著录',
-'嚷著录' => '嚷著录',
-'嚷著称' => '嚷著称',
-'嚷著稱' => '嚷著称',
-'嚷著者' => '嚷著者',
-'嚷著述' => '嚷著述',
+'諠譁' => '喧哗',
'回覆' => '回复',
-'因著' => '因着',
-'因著〈' => '因著〈',
-'因著《' => '因著《',
-'因著書' => '因著书',
-'因著书' => '因著书',
-'因著作' => '因著作',
-'因著名' => '因著名',
-'因著錄' => '因著录',
-'因著录' => '因著录',
-'因著稱' => '因著称',
-'因著称' => '因著称',
-'因著者' => '因著者',
-'因著述' => '因著述',
-'困著' => '困着',
-'困著書' => '困著书',
-'困著书' => '困著书',
-'困著作' => '困著作',
-'困著名' => '困著名',
-'困著錄' => '困著录',
-'困著录' => '困著录',
-'困著称' => '困著称',
-'困著稱' => '困著称',
-'困著者' => '困著者',
-'困著述' => '困著述',
-'圍著' => '围着',
-'圍著書' => '围著书',
-'圍著作' => '围著作',
-'圍著名' => '围著名',
-'圍著錄' => '围著录',
-'圍著稱' => '围著称',
-'圍著者' => '围著者',
-'圍著述' => '围著述',
'土著' => '土著',
-'在著' => '在着',
-'在著書' => '在著书',
-'在著书' => '在著书',
-'在著作' => '在著作',
-'在著名' => '在著名',
-'在著錄' => '在著录',
-'在著录' => '在著录',
-'在著稱' => '在著称',
-'在著称' => '在著称',
-'在著者' => '在著者',
-'在著述' => '在著述',
-'坐著' => '坐着',
-'坐著书' => '坐著书',
-'坐著書' => '坐著书',
-'坐著作' => '坐著作',
-'坐著名' => '坐著名',
-'坐著录' => '坐著录',
-'坐著錄' => '坐著录',
-'坐著称' => '坐著称',
-'坐著稱' => '坐著称',
-'坐著者' => '坐著者',
-'坐著述' => '坐著述',
'坤乾' => '坤乾',
-'備著' => '备着',
-'備著書' => '备著书',
-'備著作' => '备著作',
-'備著名' => '备著名',
-'備著錄' => '备著录',
-'備著稱' => '备著称',
-'備著者' => '备著者',
-'備著述' => '备著述',
+'塔鍾' => '塔钟',
+'墨瀋' => '墨渖',
+'壁鍾' => '壁钟',
'覆查' => '复查',
'覆核' => '复核',
-'天道为乾' => '天道为乾',
+'覆检' => '复检',
+'復甦' => '复苏',
+'多鍾' => '多钟',
+'大麴' => '大曲',
+'大鍾' => '大钟',
'天道為乾' => '天道为乾',
-'太閤' => '太阁',
-'夾著' => '夹着',
-'夾著書' => '夹著书',
-'夾著作' => '夹著作',
-'夾著名' => '夹著名',
-'夾著錄' => '夹著录',
-'夾著稱' => '夹著称',
-'夾著者' => '夹著者',
-'夾著述' => '夹著述',
+'天道为乾' => '天道为乾',
'奧區' => '奧区',
-'姓幺' => '姓幺',
+'如瀋' => '如渖',
+'姓么' => '姓幺',
+'子餘' => '子馀',
'字乾生' => '字乾生',
-'存摺' => '存摺',
-'孤著' => '孤着',
-'孤著书' => '孤著书',
-'孤著書' => '孤著书',
-'孤著作' => '孤著作',
-'孤著名' => '孤著名',
-'孤著錄' => '孤著录',
-'孤著录' => '孤著录',
-'孤著称' => '孤著称',
-'孤著稱' => '孤著称',
-'孤著者' => '孤著者',
-'孤著述' => '孤著述',
-'學著' => '学着',
-'學著書' => '学著书',
-'學著作' => '学著作',
-'學著名' => '学著名',
-'學著錄' => '学著录',
-'學著稱' => '学著称',
-'學著者' => '学著者',
-'學著述' => '学著述',
-'守著' => '守着',
-'守著書' => '守著书',
-'守著书' => '守著书',
-'守著作' => '守著作',
-'守著名' => '守著名',
-'守著录' => '守著录',
-'守著錄' => '守著录',
-'守著称' => '守著称',
-'守著稱' => '守著称',
-'守著者' => '守著者',
-'守著述' => '守著述',
-'定著' => '定着',
-'定著書' => '定著书',
-'定著书' => '定著书',
-'定著作' => '定著作',
-'定著名' => '定著名',
-'定著錄' => '定著录',
-'定著录' => '定著录',
-'定著称' => '定著称',
-'定著稱' => '定著称',
-'定著者' => '定著者',
-'定著述' => '定著述',
-'對著' => '对着',
-'對著書' => '对著书',
-'對著作' => '对著作',
-'對著名' => '对著名',
-'對著錄' => '对著录',
-'對著稱' => '对著称',
-'對著者' => '对著者',
-'對著述' => '对著述',
-'尋著' => '寻着',
-'尋著書' => '寻著书',
-'尋著作' => '寻著作',
-'尋著名' => '寻著名',
-'尋著錄' => '寻著录',
-'尋著稱' => '寻著称',
-'尋著者' => '寻著者',
-'尋著述' => '寻著述',
+'孫乾' => '孙乾',
+'孙乾' => '孙乾',
+'宋鍾國' => '宋钟国',
+'宏碁' => '宏碁',
+'官陞' => '官升',
'將軍抽俥' => '将军抽俥',
'將軍抽車' => '将军抽車',
+'爾冬陞' => '尔冬升',
'尼乾陀' => '尼乾陀',
-'展著' => '展着',
-'展著書' => '展著书',
-'展著书' => '展著书',
-'展著作' => '展著作',
-'展著名' => '展著名',
-'展著錄' => '展著录',
-'展著录' => '展著录',
-'展著稱' => '展著称',
-'展著称' => '展著称',
-'展著者' => '展著者',
-'展著述' => '展著述',
-'峯岸南' => '峯岸南',
+'跼促' => '局促',
+'跼限' => '局限',
+'山崩鍾應' => '山崩钟应',
+'崔秀鍾' => '崔秀钟',
'巨著' => '巨著',
-'帶著' => '带着',
-'帶著書' => '带著书',
-'帶著作' => '带著作',
-'帶著名' => '带著名',
-'帶著錄' => '带著录',
-'帶著稱' => '带著称',
-'帶著者' => '带著者',
-'帶著述' => '带著述',
-'幫著' => '帮着',
-'幫著書' => '帮著书',
-'幫著作' => '帮著作',
-'幫著名' => '帮著名',
-'幫著錄' => '帮著录',
-'幫著稱' => '帮著称',
-'幫著者' => '帮著者',
-'幫著述' => '帮著述',
'乾乾淨淨' => '干干净净',
'乾乾脆脆' => '干干脆脆',
'乾泉水' => '干泉水',
-'幹著' => '干着',
+'年陞' => '年升',
'么二三' => '幺二三',
-'幺二三' => '幺二三',
'么元' => '幺元',
-'幺元' => '幺元',
-'幺鳳' => '幺凤',
'么鳳' => '幺凤',
+'么半' => '幺半',
'么半群' => '幺半群',
-'幺半群' => '幺半群',
-'幺廝' => '幺厮',
+'么廝' => '幺厮',
'幺厮' => '幺厮',
-'幺叔' => '幺叔',
'么叔' => '幺叔',
'么媽' => '幺妈',
-'幺媽' => '幺妈',
'么妹' => '幺妹',
-'幺妹' => '幺妹',
'么姓' => '幺姓',
-'幺姓' => '幺姓',
'么姨' => '幺姨',
-'幺姨' => '幺姨',
'么娘' => '幺娘',
-'么孃' => '幺娘',
-'幺娘' => '幺娘',
'幺孃' => '幺娘',
-'幺小' => '幺小',
+'么孃' => '幺娘',
'么小' => '幺小',
-'幺氏' => '幺氏',
+'么弟' => '幺弟',
'么氏' => '幺氏',
'么爸' => '幺爸',
-'幺爸' => '幺爸',
-'幺爹' => '幺爹',
'么爹' => '幺爹',
'么篇' => '幺篇',
-'幺篇' => '幺篇',
'么舅' => '幺舅',
-'幺舅' => '幺舅',
'么蛾子' => '幺蛾子',
-'幺蛾子' => '幺蛾子',
'么謙' => '幺谦',
-'幺謙' => '幺谦',
-'幺麽' => '幺麽',
+'么麽' => '幺麽',
'么麼' => '幺麽',
-'幺麽小丑' => '幺麽小丑',
-'么麼小丑' => '幺麽小丑',
-'庇護著' => '庇护着',
-'應著' => '应着',
-'應著書' => '应著书',
-'應著作' => '应著作',
-'應著名' => '应著名',
-'應著錄' => '应著录',
-'應著稱' => '应著称',
-'應著者' => '应著者',
-'應著述' => '应著述',
+'么麽小丑' => '幺麽小丑',
+'慶餘' => '庆馀',
+'座鍾' => '座钟',
'康乾' => '康乾',
-'康著' => '康着',
-'康著书' => '康著书',
-'康著書' => '康著书',
-'康著作' => '康著作',
-'康著名' => '康著名',
-'康著录' => '康著录',
-'康著錄' => '康著录',
-'康著称' => '康著称',
-'康著稱' => '康著称',
-'康著者' => '康著者',
-'康著述' => '康著述',
-'開著' => '开着',
-'開著書' => '开著书',
-'開著作' => '开著作',
-'開著名' => '开著名',
-'開著錄' => '开著录',
-'開著稱' => '开著称',
-'開著者' => '开著者',
-'開著述' => '开著述',
'張法乾' => '张法乾',
'张法乾' => '张法乾',
-'當著' => '当着',
-'當著書' => '当著书',
-'當著作' => '当著作',
-'當著名' => '当著名',
-'當著錄' => '当著录',
-'當著稱' => '当著称',
-'當著者' => '当著者',
-'當著述' => '当著述',
+'張鍾英' => '张钟英',
'彰明較著' => '彰明较著',
'待覆' => '待复',
-'待著' => '待着',
-'待著书' => '待著书',
-'待著書' => '待著书',
-'待著作' => '待著作',
-'待著名' => '待著名',
-'待著录' => '待著录',
-'待著錄' => '待著录',
-'待著稱' => '待著称',
-'待著称' => '待著称',
-'待著者' => '待著者',
-'待著述' => '待著述',
'後姓' => '後姓',
-'得著' => '得着',
-'得著書' => '得著书',
-'得著书' => '得著书',
-'得著作' => '得著作',
-'得著名' => '得著名',
-'得著錄' => '得著录',
-'得著录' => '得著录',
-'得著稱' => '得著称',
-'得著称' => '得著称',
-'得著者' => '得著者',
-'得著述' => '得著述',
-'循著' => '循着',
-'循著书' => '循著书',
-'循著書' => '循著书',
-'循著作' => '循著作',
-'循著名' => '循著名',
-'循著录' => '循著录',
-'循著錄' => '循著录',
-'循著称' => '循著称',
-'循著稱' => '循著称',
-'循著者' => '循著者',
-'循著述' => '循著述',
-'心著' => '心着',
-'心著书' => '心著书',
-'心著書' => '心著书',
-'心著作' => '心著作',
-'心著名' => '心著名',
-'心著录' => '心著录',
-'心著錄' => '心著录',
-'心著稱' => '心著称',
-'心著称' => '心著称',
-'心著者' => '心著者',
-'心著述' => '心著述',
-'忍著' => '忍着',
-'忍著书' => '忍著书',
-'忍著書' => '忍著书',
-'忍著作' => '忍著作',
-'忍著名' => '忍著名',
-'忍著录' => '忍著录',
-'忍著錄' => '忍著录',
-'忍著稱' => '忍著称',
-'忍著称' => '忍著称',
-'忍著者' => '忍著者',
-'忍著述' => '忍著述',
-'志著' => '志着',
-'志著書' => '志著书',
-'志著书' => '志著书',
-'志著作' => '志著作',
-'志著名' => '志著名',
-'志著錄' => '志著录',
-'志著录' => '志著录',
-'志著称' => '志著称',
-'志著稱' => '志著称',
-'志著者' => '志著者',
-'志著述' => '志著述',
-'忙著' => '忙着',
-'忙著书' => '忙著书',
-'忙著書' => '忙著书',
-'忙著作' => '忙著作',
-'忙著名' => '忙著名',
-'忙著录' => '忙著录',
-'忙著錄' => '忙著录',
-'忙著称' => '忙著称',
-'忙著稱' => '忙著称',
-'忙著者' => '忙著者',
-'忙著述' => '忙著述',
-'懷著' => '怀着',
-'懷著書' => '怀著书',
-'懷著作' => '怀著作',
-'懷著名' => '怀著名',
-'懷著錄' => '怀著录',
-'懷著稱' => '怀著称',
-'懷著者' => '怀著者',
-'懷著述' => '怀著述',
-'急著' => '急着',
-'急著书' => '急著书',
-'急著書' => '急著书',
-'急著作' => '急著作',
-'急著名' => '急著名',
-'急著录' => '急著录',
-'急著錄' => '急著录',
-'急著称' => '急著称',
-'急著稱' => '急著称',
-'急著者' => '急著者',
-'急著述' => '急著述',
-'性著' => '性着',
-'性著书' => '性著书',
-'性著書' => '性著书',
-'性著作' => '性著作',
-'性著名' => '性著名',
-'性著录' => '性著录',
-'性著錄' => '性著录',
-'性著称' => '性著称',
-'性著稱' => '性著称',
-'性著者' => '性著者',
-'性著述' => '性著述',
-'戀著' => '恋着',
-'戀著書' => '恋著书',
-'戀著作' => '恋著作',
-'戀著名' => '恋著名',
-'戀著錄' => '恋著录',
-'戀著稱' => '恋著称',
-'戀著者' => '恋著者',
-'戀著述' => '恋著述',
+'慫慂' => '怂恿',
'恩威並著' => '恩威并著',
-'悠著' => '悠着',
-'悠著書' => '悠著书',
-'悠著书' => '悠著书',
-'悠著作' => '悠著作',
-'悠著名' => '悠著名',
-'悠著錄' => '悠著录',
-'悠著录' => '悠著录',
-'悠著称' => '悠著称',
-'悠著稱' => '悠著称',
-'悠著者' => '悠著者',
-'悠著述' => '悠著述',
-'慣著' => '惯着',
-'慣著書' => '惯著书',
-'慣著作' => '惯著作',
-'慣著名' => '惯著名',
-'慣著錄' => '惯著录',
-'慣著稱' => '惯著称',
-'慣著者' => '惯著者',
-'慣著述' => '惯著述',
-'想著' => '想着',
-'想著書' => '想著书',
-'想著书' => '想著书',
-'想著作' => '想著作',
-'想著名' => '想著名',
-'想著錄' => '想著录',
-'想著录' => '想著录',
-'想著称' => '想著称',
-'想著稱' => '想著称',
-'想著者' => '想著者',
-'想著述' => '想著述',
-'戰著' => '战着',
-'戰著書' => '战著书',
-'戰著作' => '战著作',
-'戰著名' => '战著名',
-'戰著錄' => '战著录',
-'戰著稱' => '战著称',
-'戰著者' => '战著者',
-'戰著述' => '战著述',
-'戴著' => '戴着',
-'戴著書' => '戴著书',
-'戴著书' => '戴著书',
-'戴著作' => '戴著作',
-'戴著名' => '戴著名',
-'戴著錄' => '戴著录',
-'戴著录' => '戴著录',
-'戴著稱' => '戴著称',
-'戴著称' => '戴著称',
-'戴著者' => '戴著者',
-'戴著述' => '戴著述',
-'扎著' => '扎着',
-'扎著書' => '扎著书',
-'扎著书' => '扎著书',
-'扎著作' => '扎著作',
-'扎著名' => '扎著名',
-'扎著錄' => '扎著录',
-'扎著录' => '扎著录',
-'扎著称' => '扎著称',
-'扎著稱' => '扎著称',
-'扎著者' => '扎著者',
-'扎著述' => '扎著述',
-'打著' => '打着',
-'打著書' => '打著书',
-'打著书' => '打著书',
-'打著作' => '打著作',
-'打著名' => '打著名',
-'打著錄' => '打著录',
-'打著录' => '打著录',
-'打著称' => '打著称',
-'打著稱' => '打著称',
-'打著者' => '打著者',
-'打著述' => '打著述',
-'扛著' => '扛着',
-'扛著书' => '扛著书',
-'扛著書' => '扛著书',
-'扛著作' => '扛著作',
-'扛著名' => '扛著名',
-'扛著录' => '扛著录',
-'扛著錄' => '扛著录',
-'扛著称' => '扛著称',
-'扛著稱' => '扛著称',
-'扛著者' => '扛著者',
-'扛著述' => '扛著述',
+'噁心' => '恶心',
+'懸鍾' => '悬钟',
+'情蒐' => '情搜',
+'情鍾' => '情钟',
+'惏悷' => '惏悷',
+'惏慄' => '惏慄',
+'慘澹' => '惨淡',
+'成效顯著' => '成效显著',
+'成績顯著' => '成绩显著',
+'所鍾' => '所钟',
+'手鍊' => '手链',
+'扞格' => '扞格',
'執著' => '执著',
'批覆' => '批复',
-'找不著' => '找不着',
-'找得著' => '找得着',
-'抓著' => '抓着',
-'抓著作' => '抓著作',
-'抓著名' => '抓著名',
-'抓著录' => '抓著录',
-'抓著錄' => '抓著录',
-'抓著称' => '抓著称',
-'抓著稱' => '抓著称',
-'抓著者' => '抓著者',
-'抓著述' => '抓著述',
-'護著' => '护着',
-'護著書' => '护著书',
-'護著作' => '护著作',
-'護著名' => '护著名',
-'護著錄' => '护著录',
-'護著稱' => '护著称',
-'護著者' => '护著者',
-'護著述' => '护著述',
-'披著' => '披着',
-'披著书' => '披著书',
-'披著書' => '披著书',
-'披著作' => '披著作',
-'披著名' => '披著名',
-'披著录' => '披著录',
-'披著錄' => '披著录',
-'披著稱' => '披著称',
-'披著称' => '披著称',
-'披著者' => '披著者',
-'披著述' => '披著述',
-'抬著' => '抬着',
-'抬著作' => '抬著作',
-'抬著名' => '抬著名',
-'抬著录' => '抬著录',
-'抬著錄' => '抬著录',
-'抬著稱' => '抬著称',
-'抬著称' => '抬著称',
-'抬著者' => '抬著者',
-'抬著述' => '抬著述',
-'抱著' => '抱着',
-'抱著作' => '抱著作',
-'抱著名' => '抱著名',
-'抱著录' => '抱著录',
-'抱著錄' => '抱著录',
-'抱著稱' => '抱著称',
-'抱著称' => '抱著称',
-'抱著者' => '抱著者',
-'抱著述' => '抱著述',
-'拉著' => '拉着',
-'拉著书' => '拉著书',
-'拉著書' => '拉著书',
-'拉著作' => '拉著作',
-'拉著名' => '拉著名',
-'拉著录' => '拉著录',
-'拉著錄' => '拉著录',
-'拉著称' => '拉著称',
-'拉著稱' => '拉著称',
-'拉著者' => '拉著者',
-'拉著述' => '拉著述',
'拉鍊' => '拉链',
-'拎著' => '拎着',
-'拎著作' => '拎著作',
-'拎著名' => '拎著名',
-'拎著錄' => '拎著录',
-'拎著录' => '拎著录',
-'拎著称' => '拎著称',
-'拎著稱' => '拎著称',
-'拎著者' => '拎著者',
-'拎著述' => '拎著述',
-'拖著' => '拖着',
-'拖著作' => '拖著作',
-'拖著名' => '拖著名',
-'拖著錄' => '拖著录',
-'拖著录' => '拖著录',
-'拖著稱' => '拖著称',
-'拖著称' => '拖著称',
-'拖著者' => '拖著者',
-'拖著述' => '拖著述',
'拙著' => '拙著',
'拚命' => '拚命',
'拚搏' => '拚搏',
'拚死' => '拚死',
-'拼著' => '拼着',
-'拼著作' => '拼著作',
-'拼著名' => '拼著名',
-'拼著录' => '拼著录',
-'拼著錄' => '拼著录',
-'拼著稱' => '拼著称',
-'拼著称' => '拼著称',
-'拼著者' => '拼著者',
-'拼著述' => '拼著述',
-'拿著' => '拿着',
-'拿著作' => '拿著作',
-'拿著名' => '拿著名',
-'拿著录' => '拿著录',
-'拿著錄' => '拿著录',
-'拿著称' => '拿著称',
-'拿著稱' => '拿著称',
-'拿著者' => '拿著者',
-'拿著述' => '拿著述',
-'持著' => '持着',
-'持著作' => '持著作',
-'持著名' => '持著名',
-'持著錄' => '持著录',
-'持著录' => '持著录',
-'持著称' => '持著称',
-'持著稱' => '持著称',
-'持著者' => '持著者',
-'持著述' => '持著述',
-'挑著' => '挑着',
-'挑著作' => '挑著作',
-'挑著名' => '挑著名',
-'挑著錄' => '挑著录',
-'挑著录' => '挑著录',
-'挑著称' => '挑著称',
-'挑著稱' => '挑著称',
-'挑著者' => '挑著者',
-'挑著述' => '挑著述',
-'擋著' => '挡着',
-'擋著作' => '挡著作',
-'擋著名' => '挡著名',
-'擋著錄' => '挡著录',
-'擋著稱' => '挡著称',
-'擋著者' => '挡著者',
-'擋著述' => '挡著述',
-'掙著' => '挣着',
-'掙著書' => '挣著书',
-'掙著作' => '挣著作',
-'掙著名' => '挣著名',
-'掙著錄' => '挣著录',
-'掙著稱' => '挣著称',
-'掙著者' => '挣著者',
-'掙著述' => '挣著述',
-'揮著' => '挥着',
-'揮著作' => '挥著作',
-'揮著名' => '挥著名',
-'揮著錄' => '挥著录',
-'揮著稱' => '挥著称',
-'揮著者' => '挥著者',
-'揮著述' => '挥著述',
-'挨著' => '挨着',
-'挨著作' => '挨著作',
-'挨著名' => '挨著名',
-'挨著錄' => '挨著录',
-'挨著录' => '挨著录',
-'挨著稱' => '挨著称',
-'挨著称' => '挨著称',
-'挨著者' => '挨著者',
-'挨著述' => '挨著述',
-'捆著' => '捆着',
-'捆著作' => '捆著作',
-'捆著名' => '捆著名',
-'捆著錄' => '捆著录',
-'捆著录' => '捆著录',
-'捆著称' => '捆著称',
-'捆著稱' => '捆著称',
-'捆著者' => '捆著者',
-'捆著述' => '捆著述',
-'據著' => '据着',
-'據著書' => '据著书',
-'據著作' => '据著作',
-'據著名' => '据著名',
-'據著錄' => '据著录',
-'據著稱' => '据著称',
-'據著者' => '据著者',
-'據著述' => '据著述',
-'掖著' => '掖着',
-'掖著作' => '掖著作',
-'掖著名' => '掖著名',
-'掖著錄' => '掖著录',
-'掖著录' => '掖著录',
-'掖著稱' => '掖著称',
-'掖著称' => '掖著称',
-'掖著者' => '掖著者',
-'掖著述' => '掖著述',
-'接著' => '接着',
-'接著作' => '接著作',
-'接著名' => '接著名',
-'接著錄' => '接著录',
-'接著录' => '接著录',
-'接著稱' => '接著称',
-'接著称' => '接著称',
-'接著者' => '接著者',
-'接著述' => '接著述',
-'揉著' => '揉着',
-'揉著书' => '揉著书',
-'揉著書' => '揉著书',
-'揉著作' => '揉著作',
-'揉著名' => '揉著名',
-'揉著录' => '揉著录',
-'揉著錄' => '揉著录',
-'揉著称' => '揉著称',
-'揉著稱' => '揉著称',
-'揉著者' => '揉著者',
-'揉著述' => '揉著述',
-'提著' => '提着',
-'提著作' => '提著作',
-'提著名' => '提著名',
-'提著錄' => '提著录',
-'提著录' => '提著录',
-'提著稱' => '提著称',
-'提著称' => '提著称',
-'提著者' => '提著者',
-'提著述' => '提著述',
-'摟著' => '搂着',
-'摟著作' => '搂著作',
-'摟著名' => '搂著名',
-'摟著錄' => '搂著录',
-'摟著稱' => '搂著称',
-'摟著者' => '搂著者',
-'摟著述' => '搂著述',
-'擺著' => '摆着',
-'擺著作' => '摆著作',
-'擺著名' => '摆著名',
-'擺著錄' => '摆著录',
-'擺著稱' => '摆著称',
-'擺著者' => '摆著者',
-'擺著述' => '摆著述',
+'拾瀋' => '拾渖',
+'拿破崙' => '拿破仑',
+'掛鍾' => '挂钟',
+'挨剋' => '挨剋',
+'掩耳盜鍾' => '掩耳盗钟',
+'提昇' => '提升',
+'蒐錄' => '搜录',
+'蒐索' => '搜索',
+'蒐羅' => '搜罗',
+'蒐藏' => '搜藏',
+'蒐證' => '搜证',
+'蒐購' => '搜购',
+'蒐輯' => '搜辑',
+'蒐采' => '搜采',
+'蒐採' => '搜采',
+'蒐集' => '搜集',
+'搥打' => '搥打',
+'搥胸頓足' => '搥胸顿足',
+'擺鍾' => '摆钟',
+'撞鍾' => '撞钟',
'撰著' => '撰著',
-'撼著' => '撼着',
-'撼著書' => '撼著书',
-'撼著书' => '撼著书',
-'撼著作' => '撼著作',
-'撼著名' => '撼著名',
-'撼著錄' => '撼著录',
-'撼著录' => '撼著录',
-'撼著称' => '撼著称',
-'撼著稱' => '撼著称',
-'撼著者' => '撼著者',
-'撼著述' => '撼著述',
-'敞著' => '敞着',
-'敞著作' => '敞著作',
-'敞著名' => '敞著名',
-'敞著錄' => '敞著录',
-'敞著录' => '敞著录',
-'敞著稱' => '敞著称',
-'敞著称' => '敞著称',
-'敞著者' => '敞著者',
-'敞著述' => '敞著述',
-'數著' => '数着',
-'數著作' => '数著作',
-'數著名' => '数著名',
-'數著錄' => '数著录',
-'數著稱' => '数著称',
-'數著者' => '数著者',
-'數著述' => '数著述',
-'斗著' => '斗着',
-'斗著書' => '斗著书',
-'斗著书' => '斗著书',
-'斗著作' => '斗著作',
-'斗著名' => '斗著名',
-'斗著錄' => '斗著录',
-'斗著录' => '斗著录',
-'斗著称' => '斗著称',
-'斗著稱' => '斗著称',
-'斗著者' => '斗著者',
-'斗著述' => '斗著述',
-'斥著' => '斥着',
-'斥著書' => '斥著书',
-'斥著书' => '斥著书',
-'斥著作' => '斥著作',
-'斥著名' => '斥著名',
-'斥著錄' => '斥著录',
-'斥著录' => '斥著录',
-'斥著稱' => '斥著称',
-'斥著称' => '斥著称',
-'斥著者' => '斥著者',
-'斥著述' => '斥著述',
+'效果顯著' => '效果显著',
+'敲鍾' => '敲钟',
+'文徵明' => '文徵明',
'新著' => '新著',
-'新著龍虎門' => '新著龙虎门',
'於世成' => '於世成',
+'於之瑩' => '於之莹',
+'於之莹' => '於之莹',
'於乎' => '於乎',
'於乙于同' => '於乙于同',
'於乙宇同' => '於乙宇同',
@@ -14643,838 +13575,147 @@ $zh2Hans = array(
'於梨華' => '於梨华',
'於梨华' => '於梨华',
'於氏' => '於氏',
-'於潛縣' => '於潜县',
'於潜县' => '於潜县',
+'於潛縣' => '於潜县',
'於祥玉' => '於祥玉',
'於菟' => '於菟',
'於賢德' => '於贤德',
'於除鞬' => '於除鞬',
-'旋乾转坤' => '旋乾转坤',
'旋乾轉坤' => '旋乾转坤',
+'旋乾转坤' => '旋乾转坤',
+'時鍾' => '时钟',
'曠若發矇' => '旷若发矇',
-'昂著' => '昂着',
-'昂著书' => '昂著书',
-'昂著書' => '昂著书',
-'昂著作' => '昂著作',
-'昂著名' => '昂著名',
-'昂著錄' => '昂著录',
-'昂著录' => '昂著录',
-'昂著稱' => '昂著称',
-'昂著称' => '昂著称',
-'昂著者' => '昂著者',
-'昂著述' => '昂著述',
+'崑崙' => '昆仑',
+'崑劇' => '昆剧',
+'崑山' => '昆山',
+'崑曲' => '昆曲',
+'崑腔' => '昆腔',
+'崑蘇' => '昆苏',
+'崑調' => '昆调',
'易·乾' => '易·乾',
-'易經·乾' => '易经·乾',
'易经·乾' => '易经·乾',
-'易經乾' => '易经乾',
+'易經·乾' => '易经·乾',
'易经乾' => '易经乾',
-'映著' => '映着',
-'映著書' => '映著书',
-'映著书' => '映著书',
-'映著作' => '映著作',
-'映著名' => '映著名',
-'映著錄' => '映著录',
-'映著录' => '映著录',
-'映著稱' => '映著称',
-'映著称' => '映著称',
-'映著者' => '映著者',
-'映著述' => '映著述',
+'易經乾' => '易经乾',
'昭著' => '昭著',
'顯著' => '显著',
-'显著' => '显著',
-'晃著' => '晃着',
-'晃著作' => '晃著作',
-'晃著名' => '晃著名',
-'晃著錄' => '晃著录',
-'晃著录' => '晃著录',
-'晃著称' => '晃著称',
-'晃著稱' => '晃著称',
-'晃著者' => '晃著者',
-'晃著述' => '晃著述',
-'暗著' => '暗着',
-'暗著书' => '暗著书',
-'暗著書' => '暗著书',
-'暗著作' => '暗著作',
-'暗著名' => '暗著名',
-'暗著錄' => '暗著录',
-'暗著录' => '暗著录',
-'暗著称' => '暗著称',
-'暗著稱' => '暗著称',
-'暗著者' => '暗著者',
-'暗著述' => '暗著述',
-'有著' => '有着',
-'有著書' => '有著书',
-'有著书' => '有著书',
-'有著作' => '有著作',
-'有著名' => '有著名',
-'有著錄' => '有著录',
-'有著录' => '有著录',
-'有著称' => '有著称',
-'有著稱' => '有著称',
-'有著者' => '有著者',
-'有著述' => '有著述',
-'望著' => '望着',
-'望著作' => '望著作',
-'望著名' => '望著名',
-'望著录' => '望著录',
-'望著錄' => '望著录',
-'望著稱' => '望著称',
-'望著称' => '望著称',
-'望著者' => '望著者',
-'望著述' => '望著述',
+'顯著地' => '显著地',
+'顯著地位' => '显著地位',
+'顯著性' => '显著性',
+'顯著成績' => '显著成绩',
+'顯著效果' => '显著效果',
+'顯著特點' => '显著特点',
+'晉陞' => '晋升',
+'晚鍾' => '晚钟',
+'晨鍾' => '晨钟',
+'暗闇' => '暗闇',
+'麴黴' => '曲霉',
+'曾運乾' => '曾运乾',
+'曾运乾' => '曾运乾',
+'月陞' => '月升',
'朝乾夕惕' => '朝乾夕惕',
-'朝著' => '朝着',
-'朝著作' => '朝著作',
-'朝著名' => '朝著名',
-'朝著录' => '朝著录',
-'朝著錄' => '朝著录',
-'朝著稱' => '朝著称',
-'朝著称' => '朝著称',
-'朝著者' => '朝著者',
-'朝著述' => '朝著述',
-'本著' => '本着',
-'本著书' => '本著书',
-'本著書' => '本著书',
-'本著作' => '本著作',
-'本著名' => '本著名',
-'本著录' => '本著录',
-'本著錄' => '本著录',
-'本著稱' => '本著称',
-'本著称' => '本著称',
-'本著者' => '本著者',
-'本著述' => '本著述',
+'朝鍾暮鼓' => '朝钟暮鼓',
+'朱有燉' => '朱有燉',
+'朱淛' => '朱淛',
'朴於宇同' => '朴於宇同',
-'殺著' => '杀着',
-'殺著書' => '杀著书',
-'殺著作' => '杀著作',
-'殺著名' => '杀著名',
-'殺著錄' => '杀著录',
-'殺著稱' => '杀著称',
-'殺著者' => '杀著者',
-'殺著述' => '杀著述',
-'雜著' => '杂着',
-'雜著書' => '杂著书',
-'雜著作' => '杂著作',
-'雜著名' => '杂著名',
-'雜著錄' => '杂著录',
-'雜著稱' => '杂著称',
-'雜著者' => '杂著者',
-'雜著述' => '杂著述',
'李乾德' => '李乾德',
'李乾順' => '李乾顺',
'李乾顺' => '李乾顺',
'李澤鉅' => '李泽钜',
-'來著' => '来着',
-'來著書' => '来著书',
-'來著作' => '来著作',
-'來著名' => '来著名',
-'來著錄' => '来著录',
-'來著稱' => '来著称',
-'來著者' => '来著者',
-'來著述' => '来著述',
-'楊幺' => '杨幺',
-'枕著' => '枕着',
-'枕著作' => '枕著作',
-'枕著名' => '枕著名',
-'枕著錄' => '枕著录',
-'枕著录' => '枕著录',
-'枕著稱' => '枕著称',
-'枕著称' => '枕著称',
-'枕著者' => '枕著者',
-'枕著述' => '枕著述',
-'柳詒徵' => '柳诒徵',
+'李祕' => '李祕',
+'李譔' => '李譔',
+'李鍾原' => '李钟原',
+'林鍾' => '林钟',
'柳诒徵' => '柳诒徵',
-'標志著' => '标志着',
-'標誌著' => '标志着',
-'夢著' => '梦着',
-'夢著書' => '梦著书',
-'夢著作' => '梦著作',
-'夢著名' => '梦著名',
-'夢著錄' => '梦著录',
-'夢著稱' => '梦著称',
-'夢著者' => '梦著者',
-'夢著述' => '梦著述',
-'梳著' => '梳着',
-'梳著作' => '梳著作',
-'梳著名' => '梳著名',
-'梳著錄' => '梳著录',
-'梳著录' => '梳著录',
-'梳著稱' => '梳著称',
-'梳著称' => '梳著称',
-'梳著者' => '梳著者',
-'梳著述' => '梳著述',
+'柳詒徵' => '柳诒徵',
+'校讎' => '校雠',
+'楈枒' => '楈枒',
'樊於期' => '樊於期',
+'橡椀' => '橡椀',
+'此鍾' => '此钟',
+'殘瀋' => '残渖',
+'慇懃' => '殷勤',
+'慇勤' => '殷勤',
+'比較顯著' => '比较显著',
+'毫釐' => '毫厘',
'氆氌' => '氆氌',
-'求著' => '求着',
-'求著书' => '求著书',
-'求著書' => '求著书',
-'求著作' => '求著作',
-'求著名' => '求著名',
-'求著录' => '求著录',
-'求著錄' => '求著录',
-'求著称' => '求著称',
-'求著稱' => '求著称',
-'求著者' => '求著者',
-'求著述' => '求著述',
'沈沒' => '沉没',
-'沉著' => '沉着',
+'沈澱' => '沉淀',
'沈積' => '沉积',
'沈船' => '沉船',
-'沉著書' => '沉著书',
-'沉著书' => '沉著书',
-'沉著作' => '沉著作',
-'沉著名' => '沉著名',
-'沉著錄' => '沉著录',
-'沉著录' => '沉著录',
-'沉著称' => '沉著称',
-'沉著稱' => '沉著称',
-'沉著者' => '沉著者',
-'沉著述' => '沉著述',
+'沈重' => '沉重',
'沈默' => '沉默',
-'沿著' => '沿着',
-'沿著书' => '沿著书',
-'沿著書' => '沿著书',
-'沿著作' => '沿著作',
-'沿著名' => '沿著名',
-'沿著录' => '沿著录',
-'沿著錄' => '沿著录',
-'沿著稱' => '沿著称',
-'沿著称' => '沿著称',
-'沿著者' => '沿著者',
-'沿著述' => '沿著述',
'氾濫' => '泛滥',
'洗鍊' => '洗练',
-'活著' => '活着',
-'活著书' => '活著书',
-'活著書' => '活著书',
-'活著作' => '活著作',
-'活著名' => '活著名',
-'活著录' => '活著录',
-'活著錄' => '活著录',
-'活著稱' => '活著称',
-'活著称' => '活著称',
-'活著者' => '活著者',
-'活著述' => '活著述',
-'流著' => '流着',
-'流著书' => '流著书',
-'流著書' => '流著书',
-'流著作' => '流著作',
-'流著名' => '流著名',
-'流著录' => '流著录',
-'流著錄' => '流著录',
-'流著稱' => '流著称',
-'流著称' => '流著称',
-'流著者' => '流著者',
-'流著述' => '流著述',
-'流露著' => '流露着',
-'浮著' => '浮着',
-'浮著书' => '浮著书',
-'浮著書' => '浮著书',
-'浮著作' => '浮著作',
-'浮著名' => '浮著名',
-'浮著录' => '浮著录',
-'浮著錄' => '浮著录',
-'浮著稱' => '浮著称',
-'浮著称' => '浮著称',
-'浮著者' => '浮著者',
-'浮著述' => '浮著述',
-'潤著' => '润着',
-'潤著書' => '润著书',
-'潤著作' => '润著作',
-'潤著名' => '润著名',
-'潤著錄' => '润著录',
-'潤著稱' => '润著称',
-'潤著者' => '润著者',
-'潤著述' => '润著述',
-'涵著' => '涵着',
-'涵著书' => '涵著书',
-'涵著書' => '涵著书',
-'涵著作' => '涵著作',
-'涵著名' => '涵著名',
-'涵著录' => '涵著录',
-'涵著錄' => '涵著录',
-'涵著稱' => '涵著称',
-'涵著称' => '涵著称',
-'涵著者' => '涵著者',
-'涵著述' => '涵著述',
-'渴著' => '渴着',
-'渴著书' => '渴著书',
-'渴著書' => '渴著书',
-'渴著作' => '渴著作',
-'渴著名' => '渴著名',
-'渴著录' => '渴著录',
-'渴著錄' => '渴著录',
-'渴著称' => '渴著称',
-'渴著稱' => '渴著称',
-'渴著者' => '渴著者',
-'渴著述' => '渴著述',
-'溢著' => '溢着',
-'溢著書' => '溢著书',
-'溢著书' => '溢著书',
-'溢著作' => '溢著作',
-'溢著名' => '溢著名',
-'溢著錄' => '溢著录',
-'溢著录' => '溢著录',
-'溢著称' => '溢著称',
-'溢著稱' => '溢著称',
-'溢著者' => '溢著者',
-'溢著述' => '溢著述',
-'演著' => '演着',
-'演著书' => '演著书',
-'演著書' => '演著书',
-'演著作' => '演著作',
-'演著名' => '演著名',
-'演著录' => '演著录',
-'演著錄' => '演著录',
-'演著稱' => '演著称',
-'演著称' => '演著称',
-'演著者' => '演著者',
-'演著述' => '演著述',
-'漫著' => '漫着',
-'漫著書' => '漫著书',
-'漫著书' => '漫著书',
-'漫著作' => '漫著作',
-'漫著名' => '漫著名',
-'漫著录' => '漫著录',
-'漫著錄' => '漫著录',
-'漫著称' => '漫著称',
-'漫著稱' => '漫著称',
-'漫著者' => '漫著者',
-'漫著述' => '漫著述',
-'點著' => '点着',
-'點著作' => '点著作',
-'點著名' => '点著名',
-'點著錄' => '点著录',
-'點著稱' => '点著称',
-'點著者' => '点著者',
-'點著述' => '点著述',
-'燒著' => '烧着',
-'燒著作' => '烧著作',
-'燒著名' => '烧著名',
-'燒著錄' => '烧著录',
-'燒著稱' => '烧著称',
-'燒著者' => '烧著者',
-'燒著述' => '烧著述',
-'照著' => '照着',
-'照著书' => '照著书',
-'照著書' => '照著书',
-'照著作' => '照著作',
-'照著名' => '照著名',
-'照著录' => '照著录',
-'照著錄' => '照著录',
-'照著称' => '照著称',
-'照著稱' => '照著称',
-'照著者' => '照著者',
-'照著述' => '照著述',
-'愛護著' => '爱护着',
-'愛著' => '爱着',
-'愛著書' => '爱著书',
-'愛著作' => '爱著作',
-'愛著名' => '爱著名',
-'愛著錄' => '爱著录',
-'愛著稱' => '爱著称',
-'愛著者' => '爱著者',
-'愛著述' => '爱著述',
-'牽著' => '牵着',
-'牽著書' => '牵著书',
-'牽著作' => '牵著作',
-'牽著名' => '牵著名',
-'牽著錄' => '牵著录',
-'牽著稱' => '牵著称',
-'牽著者' => '牵著者',
-'牽著述' => '牵著述',
-'犯不著' => '犯不着',
-'犯得著' => '犯得着',
-'獨著' => '独着',
-'獨著書' => '独著书',
-'獨著作' => '独著作',
-'獨著名' => '独著名',
-'獨著錄' => '独著录',
-'獨著稱' => '独著称',
-'獨著者' => '独著者',
-'獨著述' => '独著述',
-'猜著' => '猜着',
-'猜著書' => '猜着书',
-'猜著作' => '猜著作',
-'猜著名' => '猜著名',
-'猜著錄' => '猜著录',
-'猜著录' => '猜著录',
-'猜著称' => '猜著称',
-'猜著稱' => '猜著称',
-'猜著者' => '猜著者',
-'猜著述' => '猜著述',
+'洪鍾' => '洪钟',
+'瀋液' => '渖液',
+'點鍾' => '点钟',
+'薰習' => '熏习',
+'薰心' => '熏心',
+'薰沐' => '熏沐',
+'薰陶' => '熏陶',
+'薰香' => '熏香',
+'爨翫' => '爨翫',
+'獨鍾' => '独钟',
'王道乾' => '王道乾',
-'玩著' => '玩着',
-'甜著' => '甜着',
-'甜著書' => '甜著书',
-'甜著书' => '甜著书',
-'甜著作' => '甜著作',
-'甜著名' => '甜著名',
-'甜著录' => '甜著录',
-'甜著錄' => '甜著录',
-'甜著稱' => '甜著称',
-'甜著称' => '甜著称',
-'甜著者' => '甜著者',
-'甜著述' => '甜著述',
-'用不著' => '用不着',
-'用得著' => '用得着',
-'用著' => '用着',
-'用著书' => '用著书',
-'用著書' => '用著书',
-'用著作' => '用著作',
-'用著名' => '用著名',
-'用著录' => '用著录',
-'用著錄' => '用著录',
-'用著称' => '用著称',
-'用著稱' => '用著称',
-'用著者' => '用著者',
-'用著述' => '用著述',
+'王餘魚' => '王馀鱼',
+'甚夥' => '甚夥',
+'生物鍾' => '生物钟',
+'電鍾' => '电钟',
+'男為乾' => '男为乾',
'男为乾' => '男为乾',
'男爲乾' => '男为乾',
-'男為乾' => '男为乾',
-'男性為乾' => '男性为乾',
'男性爲乾' => '男性为乾',
+'男性為乾' => '男性为乾',
'男性为乾' => '男性为乾',
-'留著' => '留着',
-'留著書' => '留着书',
-'留著作' => '留著作',
-'留著名' => '留著名',
-'留著錄' => '留著录',
-'留著录' => '留著录',
-'留著稱' => '留著称',
-'留著称' => '留著称',
-'留著者' => '留著者',
-'留著述' => '留著述',
-'疑著' => '疑着',
-'疑著书' => '疑著书',
-'疑著書' => '疑著书',
-'疑著作' => '疑著作',
-'疑著名' => '疑著名',
-'疑著录' => '疑著录',
-'疑著錄' => '疑著录',
-'疑著称' => '疑著称',
-'疑著稱' => '疑著称',
-'疑著者' => '疑著者',
-'疑著述' => '疑著述',
-'癥瘕' => '癥瘕',
-'皺著' => '皱着',
-'皺著書' => '皱著书',
-'皺著作' => '皱著作',
-'皺著名' => '皱著名',
-'皺著錄' => '皱著录',
-'皺著稱' => '皱著称',
-'皺著者' => '皱著者',
-'皺著述' => '皱著述',
-'盛著' => '盛着',
-'盛著书' => '盛著书',
-'盛著書' => '盛著书',
-'盛著作' => '盛著作',
-'盛著名' => '盛著名',
-'盛著錄' => '盛著录',
-'盛著录' => '盛著录',
-'盛著稱' => '盛著称',
-'盛著称' => '盛著称',
-'盛著者' => '盛著者',
-'盛著述' => '盛著述',
-'盯著' => '盯着',
-'盯著書' => '盯着书',
-'盯著作' => '盯著作',
-'盯著名' => '盯著名',
-'盯著錄' => '盯著录',
-'盯著录' => '盯著录',
-'盯著稱' => '盯著称',
-'盯著称' => '盯著称',
-'盯著者' => '盯著者',
-'盯著述' => '盯著述',
-'盾著' => '盾着',
-'盾著書' => '盾著书',
-'盾著书' => '盾著书',
-'盾著作' => '盾著作',
-'盾著名' => '盾著名',
-'盾著錄' => '盾著录',
-'盾著录' => '盾著录',
-'盾著稱' => '盾著称',
-'盾著称' => '盾著称',
-'盾著者' => '盾著者',
-'盾著述' => '盾著述',
-'看不著' => '看不着',
-'看得著' => '看得着',
-'看著' => '看着',
-'看著書' => '看着书',
-'看著作' => '看著作',
-'看著名' => '看著名',
-'看著录' => '看著录',
-'看著錄' => '看著录',
-'看著稱' => '看著称',
-'看著称' => '看著称',
-'看著者' => '看著者',
-'看著述' => '看著述',
-'著業' => '着业',
-'著絲' => '着丝',
-'著么' => '着么',
-'著人' => '着人',
-'著什么急' => '着什么急',
-'著他' => '着他',
-'著令' => '着令',
-'著位' => '着位',
-'著體' => '着体',
-'著你' => '着你',
-'著便' => '着便',
-'著涼' => '着凉',
-'著力' => '着力',
-'著勁' => '着劲',
-'著號' => '着号',
-'著呢' => '着呢',
-'著哩' => '着哩',
-'著地' => '着地',
-'著墨' => '着墨',
-'著聲' => '着声',
-'著處' => '着处',
-'著她' => '着她',
-'著妳' => '着妳',
-'著姓' => '着姓',
-'著它' => '着它',
-'著定' => '着定',
-'著實' => '着实',
-'著己' => '着己',
-'著帳' => '着帐',
-'著床' => '着床',
-'著庸' => '着庸',
-'著式' => '着式',
-'著錄' => '着录',
-'著心' => '着心',
-'著志' => '着志',
-'著忙' => '着忙',
-'著急' => '着急',
-'著惱' => '着恼',
-'著驚' => '着惊',
-'著想' => '着想',
-'著意' => '着意',
-'著慌' => '着慌',
-'著我' => '着我',
-'著手' => '着手',
-'著抹' => '着抹',
-'著摸' => '着摸',
-'著撰' => '着撰',
-'著數' => '着数',
-'著明' => '着明',
-'著末' => '着末',
-'著極' => '着极',
-'著格' => '着格',
-'著棋' => '着棋',
-'著槁' => '着槁',
-'著氣' => '着气',
-'著法' => '着法',
-'著淺' => '着浅',
-'著火' => '着火',
-'著然' => '着然',
-'著甚' => '着甚',
-'著生' => '着生',
-'著疑' => '着疑',
-'著白' => '着白',
-'著相' => '着相',
-'著眼' => '着眼',
-'著著' => '着着',
-'著祂' => '着祂',
-'著積' => '着积',
-'著稿' => '着稿',
-'著筆' => '着笔',
-'著籍' => '着籍',
-'著緊' => '着紧',
-'著緑' => '着緑',
-'著絆' => '着绊',
-'著績' => '着绩',
-'著緋' => '着绯',
-'著綠' => '着绿',
-'著肉' => '着肉',
-'著腳' => '着脚',
-'著艦' => '着舰',
-'著色' => '着色',
-'著節' => '着节',
-'著花' => '着花',
-'著莫' => '着莫',
-'著落' => '着落',
-'著藁' => '着藁',
-'著衣' => '着衣',
-'著裝' => '着装',
-'著要' => '着要',
-'著警' => '着警',
-'著趣' => '着趣',
-'著邊' => '着边',
-'著迷' => '着迷',
-'著跡' => '着迹',
-'著重' => '着重',
-'著録' => '着録',
-'著聞' => '着闻',
-'著陸' => '着陆',
-'著雝' => '着雝',
-'著鞭' => '着鞭',
-'著題' => '着题',
-'著魔' => '着魔',
-'睡不著' => '睡不着',
-'睡得著' => '睡得着',
-'睡著' => '睡着',
-'睡著書' => '睡著书',
-'睡著书' => '睡著书',
-'睡著作' => '睡著作',
-'睡著名' => '睡著名',
-'睡著錄' => '睡著录',
-'睡著录' => '睡著录',
-'睡著称' => '睡著称',
-'睡著稱' => '睡著称',
-'睡著者' => '睡著者',
-'睡著述' => '睡著述',
+'療效顯著' => '疗效显著',
+'白瀋' => '白渖',
+'皁保' => '皁保',
+'目劄' => '目劄',
+'直昇' => '直升',
'睹微知著' => '睹微知著',
-'睪丸' => '睾丸',
-'瞞著' => '瞒着',
-'瞞著書' => '瞒著书',
-'瞞著作' => '瞒著作',
-'瞞著名' => '瞒著名',
-'瞞著錄' => '瞒著录',
-'瞞著稱' => '瞒著称',
-'瞞著者' => '瞒著者',
-'瞞著述' => '瞒著述',
-'瞧著' => '瞧着',
-'瞧著書' => '瞧着书',
-'瞧著作' => '瞧著作',
-'瞧著名' => '瞧著名',
-'瞧著录' => '瞧著录',
-'瞧著錄' => '瞧著录',
-'瞧著称' => '瞧著称',
-'瞧著稱' => '瞧著称',
-'瞧著者' => '瞧著者',
-'瞧著述' => '瞧著述',
-'瞪著' => '瞪着',
-'瞪著書' => '瞪著书',
-'瞪著书' => '瞪著书',
-'瞪著作' => '瞪著作',
-'瞪著名' => '瞪著名',
-'瞪著錄' => '瞪著录',
-'瞪著录' => '瞪著录',
-'瞪著称' => '瞪著称',
-'瞪著稱' => '瞪著称',
-'瞪著者' => '瞪著者',
-'瞪著述' => '瞪著述',
+'瞭臺' => '瞭台',
+'瞭台' => '瞭台',
'瞭望' => '瞭望',
-'石碁镇' => '石碁镇',
+'矇眬' => '矇眬',
+'矇矓' => '矇眬',
+'石碁' => '石碁',
'石碁鎮' => '石碁镇',
-'福著' => '福着',
-'福著书' => '福著书',
-'福著書' => '福著书',
-'福著作' => '福著作',
-'福著名' => '福著名',
-'福著錄' => '福著录',
-'福著录' => '福著录',
-'福著稱' => '福著称',
-'福著称' => '福著称',
-'福著者' => '福著者',
-'福著述' => '福著述',
+'石英鍾' => '石英钟',
+'石鍾乳' => '石钟乳',
+'鹼菜' => '硷菜',
+'碁聖' => '碁圣',
+'碁圣' => '碁圣',
+'碁所' => '碁所',
+'祕宜' => '祕宜',
+'秒鍾' => '秒钟',
'穀梁' => '穀梁',
-'空著' => '空着',
-'空著书' => '空著书',
-'空著書' => '空著书',
-'空著作' => '空著作',
-'空著名' => '空著名',
-'空著录' => '空著录',
-'空著錄' => '空著录',
-'空著称' => '空著称',
-'空著稱' => '空著称',
-'空著者' => '空著者',
-'空著述' => '空著述',
-'穿著' => '穿着',
-'穿著书' => '穿著书',
-'穿著書' => '穿著书',
-'穿著作' => '穿著作',
-'穿著名' => '穿著名',
-'穿著录' => '穿著录',
-'穿著錄' => '穿著录',
-'穿著称' => '穿著称',
-'穿著稱' => '穿著称',
-'穿著者' => '穿著者',
-'穿著述' => '穿著述',
-'豎著' => '竖着',
-'豎著書' => '竖著书',
-'豎著作' => '竖著作',
-'豎著名' => '竖著名',
-'豎著錄' => '竖著录',
-'豎著稱' => '竖著称',
-'豎著者' => '竖著者',
-'豎著述' => '竖著述',
-'站著' => '站着',
-'站著书' => '站著书',
-'站著書' => '站著书',
-'站著作' => '站著作',
-'站著名' => '站著名',
-'站著錄' => '站著录',
-'站著录' => '站著录',
-'站著称' => '站著称',
-'站著稱' => '站著称',
-'站著者' => '站著者',
-'站著述' => '站著述',
-'笑著' => '笑着',
-'笑著书' => '笑著书',
-'笑著書' => '笑著书',
-'笑著作' => '笑著作',
-'笑著名' => '笑著名',
-'笑著录' => '笑著录',
-'笑著錄' => '笑著录',
-'笑著称' => '笑著称',
-'笑著稱' => '笑著称',
-'笑著者' => '笑著者',
-'笑著述' => '笑著述',
+'穿著者' => '穿着者',
+'竹昇' => '竹升',
'答覆' => '答复',
-'管著' => '管着',
-'管著书' => '管著书',
-'管著書' => '管著书',
-'管著作' => '管著作',
-'管著名' => '管著名',
-'管著录' => '管著录',
-'管著錄' => '管著录',
-'管著稱' => '管著称',
-'管著称' => '管著称',
-'管著者' => '管著者',
-'管著述' => '管著述',
-'米澤瑠美' => '米泽瑠美',
'米泽瑠美' => '米泽瑠美',
-'綁著' => '绑着',
-'綁著書' => '绑著书',
-'綁著作' => '绑著作',
-'綁著名' => '绑著名',
-'綁著錄' => '绑著录',
-'綁著稱' => '绑著称',
-'綁著者' => '绑著者',
-'綁著述' => '绑著述',
-'繞著' => '绕着',
-'繞著書' => '绕著书',
-'繞著作' => '绕著作',
-'繞著名' => '绕著名',
-'繞著錄' => '绕著录',
-'繞著稱' => '绕著称',
-'繞著者' => '绕著者',
-'繞著述' => '绕著述',
-'綳著勁' => '绷着劲',
-'綳著臉' => '绷着脸',
+'米瀋' => '米渖',
+'餬口' => '糊口',
+'繙㠾' => '繙㠾',
+'線國安' => '缐国安',
+'線姓' => '缐姓',
'編著' => '编著',
-'纏著' => '缠着',
-'纏著書' => '缠著书',
-'纏著作' => '缠著作',
-'纏著名' => '缠著名',
-'纏著錄' => '缠著录',
-'纏著稱' => '缠著称',
-'纏著者' => '缠著者',
-'纏著述' => '缠著述',
-'罩著' => '罩着',
-'罩著书' => '罩著书',
-'罩著書' => '罩著书',
-'罩著作' => '罩著作',
-'罩著名' => '罩著名',
-'罩著錄' => '罩著录',
-'罩著录' => '罩著录',
-'罩著称' => '罩著称',
-'罩著稱' => '罩著称',
-'罩著者' => '罩著者',
-'罩著述' => '罩著述',
-'美著' => '美着',
-'美著书' => '美著书',
-'美著書' => '美著书',
-'美著作' => '美著作',
-'美著名' => '美著名',
-'美著录' => '美著录',
-'美著錄' => '美著录',
-'美著稱' => '美著称',
-'美著称' => '美著称',
-'美著者' => '美著者',
-'美著述' => '美著述',
-'耀著' => '耀着',
-'耀著書' => '耀著书',
-'耀著书' => '耀著书',
-'耀著作' => '耀著作',
-'耀著名' => '耀著名',
-'耀著錄' => '耀著录',
-'耀著录' => '耀著录',
-'耀著称' => '耀著称',
-'耀著稱' => '耀著称',
-'耀著者' => '耀著者',
-'耀著述' => '耀著述',
-'老幺' => '老幺',
-'考著' => '考着',
-'考著書' => '考著书',
-'考著书' => '考著书',
-'考著作' => '考著作',
-'考著名' => '考著名',
-'考著錄' => '考著录',
-'考著录' => '考著录',
-'考著稱' => '考著称',
-'考著称' => '考著称',
-'考著者' => '考著者',
-'考著述' => '考著述',
+'編鍾' => '编钟',
'肉乾乾' => '肉干干',
'肘手鍊足' => '肘手链足',
-'背著' => '背着',
-'背著书' => '背著书',
-'背著書' => '背著书',
-'背著作' => '背著作',
-'背著名' => '背著名',
-'背著录' => '背著录',
-'背著錄' => '背著录',
-'背著称' => '背著称',
-'背著稱' => '背著称',
-'背著者' => '背著者',
-'背著述' => '背著述',
-'膠著' => '胶着',
-'膠著書' => '胶著书',
-'膠著作' => '胶著作',
-'膠著名' => '胶著名',
-'膠著錄' => '胶著录',
-'膠著稱' => '胶著称',
-'膠著者' => '胶著者',
-'膠著述' => '胶著述',
-'藝著' => '艺着',
-'藝著書' => '艺著书',
-'藝著作' => '艺著作',
-'藝著名' => '艺著名',
-'藝著錄' => '艺著录',
-'藝著稱' => '艺著称',
-'藝著者' => '艺著者',
-'藝著述' => '艺著述',
-'苦著' => '苦着',
-'苦著书' => '苦著书',
-'苦著書' => '苦著书',
-'苦著作' => '苦著作',
-'苦著名' => '苦著名',
-'苦著录' => '苦著录',
-'苦著錄' => '苦著录',
-'苦著稱' => '苦著称',
-'苦著称' => '苦著称',
-'苦著者' => '苦著者',
-'苦著述' => '苦著述',
+'甦醒' => '苏醒',
'苧烯' => '苧烯',
'薴烯' => '苧烯',
-'獲著' => '获着',
-'獲著書' => '获著书',
-'獲著作' => '获著作',
-'獲著名' => '获著名',
-'獲著錄' => '获著录',
-'獲著稱' => '获著称',
-'獲著者' => '获著者',
-'獲著述' => '获著述',
-'蕭乾' => '萧乾',
+'蘋果' => '苹果',
+'荠苧' => '荠苧',
+'榮陞' => '荣升',
'萧乾' => '萧乾',
-'落著' => '落着',
-'落著书' => '落著书',
-'落著書' => '落著书',
-'落著作' => '落著作',
-'落著名' => '落著名',
-'落著录' => '落著录',
-'落著錄' => '落著录',
-'落著稱' => '落著称',
-'落著称' => '落著称',
-'落著者' => '落著者',
-'落著述' => '落著述',
+'蕭乾' => '萧乾',
'著書' => '著书',
'著書立說' => '著书立说',
'著作' => '著作',
'著名' => '著名',
+'著錄' => '著录',
'著錄規則' => '著录规则',
'著文' => '著文',
'著有' => '著有',
@@ -15482,1067 +13723,869 @@ $zh2Hans = array(
'著者' => '著者',
'著身' => '著身',
'著述' => '著述',
-'蒙汗葯' => '蒙汗药',
-'蒙著' => '蒙着',
-'蒙葯' => '蒙药',
-'蒙著書' => '蒙著书',
-'蒙著书' => '蒙著书',
-'蒙著作' => '蒙著作',
-'蒙著名' => '蒙著名',
-'蒙著录' => '蒙著录',
-'蒙著錄' => '蒙著录',
-'蒙著稱' => '蒙著称',
-'蒙著称' => '蒙著称',
-'蒙著者' => '蒙著者',
-'蒙著述' => '蒙著述',
-'藏著' => '藏着',
-'藏著書' => '藏著书',
-'藏著书' => '藏著书',
-'藏著作' => '藏著作',
-'藏著名' => '藏著名',
-'藏著錄' => '藏著录',
-'藏著录' => '藏著录',
-'藏著称' => '藏著称',
-'藏著稱' => '藏著称',
-'藏著者' => '藏著者',
-'藏著述' => '藏著述',
-'蘸著' => '蘸着',
-'蘸著書' => '蘸著书',
-'蘸著书' => '蘸著书',
-'蘸著作' => '蘸著作',
-'蘸著名' => '蘸著名',
-'蘸著录' => '蘸著录',
-'蘸著錄' => '蘸著录',
-'蘸著稱' => '蘸著称',
-'蘸著称' => '蘸著称',
-'蘸著者' => '蘸著者',
-'蘸著述' => '蘸著述',
-'行著' => '行着',
-'行著书' => '行著书',
-'行著書' => '行著书',
-'行著作' => '行著作',
-'行著名' => '行著名',
-'行著录' => '行著录',
-'行著錄' => '行著录',
-'行著稱' => '行著称',
-'行著称' => '行著称',
-'行著者' => '行著者',
-'行著述' => '行著述',
-'衣著' => '衣着',
-'衣著书' => '衣著书',
-'衣著書' => '衣著书',
-'衣著作' => '衣著作',
-'衣著名' => '衣著名',
-'衣著录' => '衣著录',
-'衣著錄' => '衣著录',
-'衣著稱' => '衣著称',
-'衣著称' => '衣著称',
-'衣著者' => '衣著者',
-'衣著述' => '衣著述',
-'裝著' => '装着',
-'裝著書' => '装著书',
-'裝著作' => '装著作',
-'裝著名' => '装著名',
-'裝著錄' => '装著录',
-'裝著稱' => '装著称',
-'裝著者' => '装著者',
-'裝著述' => '装著述',
-'裹著' => '裹着',
-'裹著書' => '裹著书',
-'裹著书' => '裹著书',
-'裹著作' => '裹著作',
-'裹著名' => '裹著名',
-'裹著录' => '裹著录',
-'裹著錄' => '裹著录',
-'裹著称' => '裹著称',
-'裹著稱' => '裹著称',
-'裹著者' => '裹著者',
-'裹著述' => '裹著述',
'覆蓋' => '覆蓋',
'見微知著' => '见微知著',
-'見著' => '见着',
-'見著書' => '见著书',
-'見著作' => '见著作',
-'見著名' => '见著名',
-'見著錄' => '见著录',
-'見著稱' => '见著称',
-'見著者' => '见著者',
-'見著述' => '见著述',
+'見著' => '见著',
'視微知著' => '视微知著',
'言幾析理' => '言幾析理',
-'警戒著' => '警戒着',
-'記著' => '记着',
-'記著書' => '记著书',
-'記著作' => '记著作',
-'記著名' => '记著名',
-'記著錄' => '记著录',
-'記著稱' => '记著称',
-'記著者' => '记著者',
-'記著述' => '记著述',
+'諲譔' => '諲譔',
+'警鍾' => '警钟',
+'譩譆' => '譩譆',
'論著' => '论著',
'譯著' => '译著',
-'試著' => '试着',
-'試著書' => '试著书',
-'試著作' => '试著作',
-'試著名' => '试著名',
-'試著錄' => '试著录',
-'試著稱' => '试著称',
-'試著者' => '试著者',
-'試著述' => '试著述',
-'語著' => '语着',
-'語著書' => '语著书',
-'語著作' => '语著作',
-'語著名' => '语著名',
-'語著錄' => '语著录',
-'語著稱' => '语著称',
-'語著者' => '语著者',
-'語著述' => '语著述',
-'豫著' => '豫着',
-'豫著书' => '豫著书',
-'豫著書' => '豫著书',
-'豫著作' => '豫著作',
-'豫著名' => '豫著名',
-'豫著录' => '豫著录',
-'豫著錄' => '豫著录',
-'豫著称' => '豫著称',
-'豫著稱' => '豫著称',
-'豫著者' => '豫著者',
-'豫著述' => '豫著述',
-'貞著' => '贞着',
-'貞著書' => '贞著书',
-'貞著作' => '贞著作',
-'貞著名' => '贞著名',
-'貞著錄' => '贞著录',
-'貞著稱' => '贞著称',
-'貞著者' => '贞著者',
-'貞著述' => '贞著述',
-'走著' => '走着',
-'走著書' => '走著书',
-'走著书' => '走著书',
-'走著作' => '走著作',
-'走著名' => '走著名',
-'走著錄' => '走著录',
-'走著录' => '走著录',
-'走著稱' => '走著称',
-'走著称' => '走著称',
-'走著者' => '走著者',
-'走著述' => '走著述',
-'趕著' => '赶着',
-'趕著書' => '赶著书',
-'趕著作' => '赶著作',
-'趕著名' => '赶著名',
-'趕著錄' => '赶著录',
-'趕著稱' => '赶著称',
-'趕著者' => '赶著者',
-'趕著述' => '赶著述',
-'趴著' => '趴着',
-'趴著書' => '趴著书',
-'趴著书' => '趴著书',
-'趴著作' => '趴著作',
-'趴著名' => '趴著名',
-'趴著录' => '趴著录',
-'趴著錄' => '趴著录',
-'趴著称' => '趴著称',
-'趴著稱' => '趴著称',
-'趴著者' => '趴著者',
-'趴著述' => '趴著述',
-'躍著' => '跃着',
-'躍著書' => '跃著书',
-'躍著作' => '跃著作',
-'躍著名' => '跃著名',
-'躍著錄' => '跃著录',
-'躍著稱' => '跃著称',
-'躍著者' => '跃著者',
-'躍著述' => '跃著述',
-'跑著' => '跑着',
-'跑著書' => '跑著书',
-'跑著书' => '跑著书',
-'跑著作' => '跑著作',
-'跑著名' => '跑著名',
-'跑著录' => '跑著录',
-'跑著錄' => '跑著录',
-'跑著稱' => '跑著称',
-'跑著称' => '跑著称',
-'跑著者' => '跑著者',
-'跑著述' => '跑著述',
-'跟著' => '跟着',
-'跟著书' => '跟著书',
-'跟著書' => '跟著书',
-'跟著作' => '跟著作',
-'跟著名' => '跟著名',
-'跟著录' => '跟著录',
-'跟著錄' => '跟著录',
-'跟著称' => '跟著称',
-'跟著稱' => '跟著称',
-'跟著者' => '跟著者',
-'跟著述' => '跟著述',
-'跪著' => '跪着',
-'跪著書' => '跪著书',
-'跪著书' => '跪著书',
-'跪著作' => '跪著作',
-'跪著名' => '跪著名',
-'跪著錄' => '跪著录',
-'跪著录' => '跪著录',
-'跪著稱' => '跪著称',
-'跪著称' => '跪著称',
-'跪著者' => '跪著者',
-'跪著述' => '跪著述',
-'跳著' => '跳着',
-'跳著书' => '跳著书',
-'跳著書' => '跳著书',
-'跳著作' => '跳著作',
-'跳著名' => '跳著名',
-'跳著录' => '跳著录',
-'跳著錄' => '跳著录',
-'跳著称' => '跳著称',
-'跳著稱' => '跳著称',
-'跳著者' => '跳著者',
-'跳著述' => '跳著述',
+'謝肇淛' => '谢肇淛',
+'象乾' => '象乾',
'躊躇滿志' => '踌躇滿志',
-'踏著' => '踏着',
-'踏著書' => '踏著书',
-'踏著书' => '踏著书',
-'踏著作' => '踏著作',
-'踏著名' => '踏著名',
-'踏著錄' => '踏著录',
-'踏著录' => '踏著录',
-'踏著称' => '踏著称',
-'踏著稱' => '踏著称',
-'踏著者' => '踏著者',
-'踏著述' => '踏著述',
-'踩著' => '踩着',
-'踩著书' => '踩著书',
-'踩著書' => '踩著书',
-'踩著作' => '踩著作',
-'踩著名' => '踩著名',
-'踩著录' => '踩著录',
-'踩著錄' => '踩著录',
-'踩著稱' => '踩著称',
-'踩著称' => '踩著称',
-'踩著者' => '踩著者',
-'踩著述' => '踩著述',
-'身著' => '身着',
-'身著书' => '身著书',
-'身著書' => '身著书',
-'身著作' => '身著作',
-'身著名' => '身著名',
-'身著录' => '身著录',
-'身著錄' => '身著录',
-'身著稱' => '身著称',
-'身著称' => '身著称',
-'身著者' => '身著者',
-'身著述' => '身著述',
-'躺著' => '躺着',
-'躺著書' => '躺著书',
-'躺著书' => '躺著书',
-'躺著作' => '躺著作',
-'躺著名' => '躺著名',
-'躺著錄' => '躺著录',
-'躺著录' => '躺著录',
-'躺著称' => '躺著称',
-'躺著稱' => '躺著称',
-'躺著者' => '躺著者',
-'躺著述' => '躺著述',
-'轉著' => '转着',
-'轉著書' => '转著书',
-'轉著作' => '转著作',
-'轉著名' => '转著名',
-'轉著錄' => '转著录',
-'轉著稱' => '转著称',
-'轉著者' => '转著者',
-'轉著述' => '转著述',
-'載著' => '载着',
-'載著書' => '载著书',
-'載著作' => '载著作',
-'載著名' => '载著名',
-'載著錄' => '载著录',
-'載著稱' => '载著称',
-'載著者' => '载著者',
-'載著述' => '载著述',
'較著' => '较著',
-'達著' => '达着',
-'達著書' => '达著书',
-'達著作' => '达著作',
-'達著名' => '达著名',
-'達著錄' => '达著录',
-'達著稱' => '达著称',
-'達著者' => '达著者',
-'達著述' => '达著述',
'近角聪信' => '近角聪信',
-'近角聰信' => '近角聪信',
'这么' => '这么',
-'遠著' => '远着',
-'遠著書' => '远著书',
-'遠著作' => '远著作',
-'遠著名' => '远著名',
-'遠著錄' => '远著录',
-'遠著稱' => '远著称',
-'遠著者' => '远著者',
-'遠著述' => '远著述',
-'連著' => '连着',
-'連著書' => '连著书',
-'連著作' => '连著作',
-'連著名' => '连著名',
-'連著錄' => '连著录',
-'連著稱' => '连著称',
-'連著者' => '连著者',
-'連著述' => '连著述',
-'迫著' => '迫着',
-'追著' => '追着',
-'追著書' => '追著书',
-'追著书' => '追著书',
-'追著作' => '追著作',
-'追著名' => '追著名',
-'追著錄' => '追著录',
-'追著录' => '追著录',
-'追著称' => '追著称',
-'追著稱' => '追著称',
-'追著者' => '追著者',
-'追著述' => '追著述',
-'逆著' => '逆着',
-'逆著書' => '逆著书',
-'逆著书' => '逆著书',
-'逆著作' => '逆著作',
-'逆著名' => '逆著名',
-'逆著錄' => '逆著录',
-'逆著录' => '逆著录',
-'逆著称' => '逆著称',
-'逆著稱' => '逆著称',
-'逆著者' => '逆著者',
-'逆著述' => '逆著述',
-'逼著' => '逼着',
-'逼著書' => '逼著书',
-'逼著书' => '逼著书',
-'逼著作' => '逼著作',
-'逼著名' => '逼著名',
-'逼著錄' => '逼著录',
-'逼著录' => '逼著录',
-'逼著称' => '逼著称',
-'逼著稱' => '逼著称',
-'逼著者' => '逼著者',
-'逼著述' => '逼著述',
-'遇著' => '遇着',
-'遇著書' => '遇著书',
-'遇著书' => '遇著书',
-'遇著作' => '遇著作',
-'遇著名' => '遇著名',
-'遇著錄' => '遇著录',
-'遇著录' => '遇著录',
-'遇著稱' => '遇著称',
-'遇著称' => '遇著称',
-'遇著者' => '遇著者',
-'遇著述' => '遇著述',
+'進化鍾' => '进化钟',
+'造麴' => '造曲',
'遺著' => '遗著',
'那麽' => '那麽',
'郭子乾' => '郭子乾',
-'配著' => '配着',
-'配著書' => '配著书',
-'配著书' => '配著书',
-'配著作' => '配著作',
-'配著名' => '配著名',
-'配著錄' => '配著录',
-'配著录' => '配著录',
-'配著稱' => '配著称',
-'配著称' => '配著称',
-'配著者' => '配著者',
-'配著述' => '配著述',
-'釀著' => '酿着',
-'釀著書' => '酿著书',
-'釀著作' => '酿著作',
-'釀著名' => '酿著名',
-'釀著錄' => '酿著录',
-'釀著稱' => '酿著称',
-'釀著者' => '酿著者',
-'釀著述' => '酿著述',
-'醯壺' => '醯壶',
+'郭行餘' => '郭行馀',
+'酒麴' => '酒曲',
+'醉瀋' => '醉渖',
'醯壶' => '醯壶',
-'醯醬' => '醯酱',
+'醯壺' => '醯壶',
'醯酱' => '醯酱',
+'醯醬' => '醯酱',
'醯醋' => '醯醋',
'醯醢' => '醯醢',
'醯鸡' => '醯鸡',
'醯雞' => '醯鸡',
'重覆' => '重复',
+'金尚鍾' => '金尚钟',
+'金民鍾' => '金民钟',
+'金鍾' => '金钟',
'金鍊' => '金链',
+'鍾麗緹' => '钟丽缇',
+'鍾乳石' => '钟乳石',
+'鍾儀奏楚' => '钟仪奏楚',
+'鍾關' => '钟关',
+'鍾聲' => '钟声',
+'鍾頭' => '钟头',
+'鍾山' => '钟山',
+'鍾差' => '钟差',
+'鍾座' => '钟座',
+'鍾情' => '钟情',
+'鍾意' => '钟意',
+'鍾慧冰' => '钟慧冰',
+'鍾擺' => '钟摆',
+'鍾架' => '钟架',
+'鍾楚紅' => '钟楚红',
+'鍾樓' => '钟楼',
+'鍾漢良' => '钟汉良',
+'鍾汶' => '钟汶',
+'鍾淑慧' => '钟淑慧',
+'鍾靈' => '钟灵',
+'鍾點' => '钟点',
+'鍾愛' => '钟爱',
+'鍾琴' => '钟琴',
+'鍾相' => '钟相',
+'鍾祥' => '钟祥',
+'鍾離' => '钟离',
+'鍾表' => '钟表',
+'鍾鎮濤' => '钟镇涛',
+'鍾面' => '钟面',
+'鍾馗' => '钟馗',
+'鍾鳴漏盡' => '钟鸣漏尽',
+'鍾鳴鼎食' => '钟鸣鼎食',
+'鍾鼓' => '钟鼓',
'鐵鍊' => '铁链',
'鉸鍊' => '铰链',
'銀鍊' => '银链',
-'鋪著' => '铺着',
-'鋪著書' => '铺著书',
-'鋪著作' => '铺著作',
-'鋪著名' => '铺著名',
-'鋪著錄' => '铺著录',
-'鋪著稱' => '铺著称',
-'鋪著者' => '铺著者',
-'鋪著述' => '铺著述',
'鍊子' => '链子',
'鍊條' => '链条',
+'鍊表' => '链表',
'鍊鎖' => '链锁',
'鍊錘' => '链锤',
'鎖鍊' => '锁链',
-'鍾鍛' => '锺锻',
-'鍛鍾' => '锻锺',
'閻懷禮' => '闫怀礼',
-'閉著' => '闭着',
-'閉著書' => '闭著书',
-'閉著作' => '闭著作',
-'閉著名' => '闭著名',
-'閉著錄' => '闭著录',
-'閉著稱' => '闭著称',
-'閉著者' => '闭著者',
-'閉著述' => '闭著述',
-'閑著' => '闲着',
-'閑著書' => '闲著书',
-'閑著作' => '闲著作',
-'閑著名' => '闲著名',
-'閑著錄' => '闲著录',
-'閑著稱' => '闲著称',
-'閑著者' => '闲著者',
-'閑著述' => '闲著述',
-'聞不著' => '闻不着',
-'聞得著' => '闻得着',
-'聞著' => '闻着',
+'鬧鍾' => '闹钟',
+'陽為乾' => '阳为乾',
'阳为乾' => '阳为乾',
'陽爲乾' => '阳为乾',
-'陽為乾' => '阳为乾',
'阿部正瞭' => '阿部正瞭',
-'附著' => '附着',
-'附睪' => '附睾',
-'附著书' => '附著书',
-'附著書' => '附著书',
-'附著作' => '附著作',
-'附著名' => '附著名',
-'附著錄' => '附著录',
-'附著录' => '附著录',
-'附著称' => '附著称',
-'附著稱' => '附著称',
-'附著者' => '附著者',
-'附著述' => '附著述',
+'陆徵祥' => '陆徵祥',
+'陸徵祥' => '陆徵祥',
'陈乾生' => '陈乾生',
'陳乾生' => '陈乾生',
+'陈元扞' => '陈元扞',
+'陳元扞' => '陈元扞',
'陈公乾生' => '陈公乾生',
'陳公乾生' => '陈公乾生',
-'陋著' => '陋着',
-'陋著書' => '陋著书',
-'陋著书' => '陋著书',
-'陋著作' => '陋著作',
-'陋著名' => '陋著名',
-'陋著錄' => '陋著录',
-'陋著录' => '陋著录',
-'陋著称' => '陋著称',
-'陋著稱' => '陋著称',
-'陋著者' => '陋著者',
-'陋著述' => '陋著述',
-'陪著' => '陪着',
-'陪著书' => '陪著书',
-'陪著書' => '陪著书',
-'陪著作' => '陪著作',
-'陪著名' => '陪著名',
-'陪著录' => '陪著录',
-'陪著錄' => '陪著录',
-'陪著称' => '陪著称',
-'陪著稱' => '陪著称',
-'陪著者' => '陪著者',
-'陪著述' => '陪著述',
+'陳遇乾' => '陈遇乾',
+'陈遇乾' => '陈遇乾',
'陳堵' => '陳堵',
'陳禕' => '陳禕',
-'隨著' => '随着',
-'隨著書' => '随著书',
-'隨著作' => '随著作',
-'隨著名' => '随著名',
-'隨著錄' => '随著录',
-'隨著稱' => '随著称',
-'隨著者' => '随著者',
-'隨著述' => '随著述',
-'隔著' => '隔着',
-'隔著书' => '隔著书',
-'隔著書' => '隔著书',
-'隔著作' => '隔著作',
-'隔著名' => '隔著名',
-'隔著录' => '隔著录',
-'隔著錄' => '隔著录',
-'隔著称' => '隔著称',
-'隔著稱' => '隔著称',
-'隔著者' => '隔著者',
-'隔著述' => '隔著述',
-'隱睪' => '隱睾',
-'雅著' => '雅着',
-'雅著书' => '雅著书',
-'雅著書' => '雅著书',
-'雅著作' => '雅著作',
-'雅著名' => '雅著名',
-'雅著录' => '雅著录',
-'雅著錄' => '雅著录',
-'雅著称' => '雅著称',
-'雅著稱' => '雅著称',
-'雅著者' => '雅著者',
-'雅著述' => '雅著述',
'雍乾' => '雍乾',
-'靠著' => '靠着',
-'靠著作' => '靠著作',
-'靠著名' => '靠著名',
-'靠著錄' => '靠著录',
-'靠著录' => '靠著录',
-'靠著稱' => '靠著称',
-'靠著称' => '靠著称',
-'靠著者' => '靠著者',
-'靠著述' => '靠著述',
-'頂著' => '顶着',
-'頂著書' => '顶著书',
-'頂著作' => '顶著作',
-'頂著名' => '顶著名',
-'頂著錄' => '顶著录',
-'頂著稱' => '顶著称',
-'頂著者' => '顶著者',
-'頂著述' => '顶著述',
+'讎夷' => '雠夷',
+'讎定' => '雠定',
+'讎校' => '雠校',
+'讎問' => '雠问',
+'音聲如鍾' => '音声如钟',
'項鍊' => '项链',
-'順著' => '顺着',
-'順著書' => '顺著书',
-'順著作' => '顺著作',
-'順著名' => '顺著名',
-'順著錄' => '顺著录',
-'順著稱' => '顺著称',
-'順著者' => '顺著者',
-'順著述' => '顺著述',
-'領著' => '领着',
-'領著書' => '领著书',
-'領著作' => '领著作',
-'領著名' => '领著名',
-'領著錄' => '领著录',
-'領著稱' => '领著称',
-'領著者' => '领著者',
-'領著述' => '领著述',
-'飄著' => '飘着',
-'飄著書' => '飘著书',
-'飄著作' => '飘著作',
-'飄著名' => '飘著名',
-'飄著錄' => '飘著录',
-'飄著稱' => '飘著称',
-'飄著者' => '飘著者',
-'飄著述' => '飘著述',
+'飛昇' => '飞升',
'飭令' => '飭令',
-'駕著' => '驾着',
-'駕著書' => '驾著书',
-'駕著作' => '驾著作',
-'駕著名' => '驾著名',
-'駕著錄' => '驾著录',
-'駕著稱' => '驾著称',
-'駕著者' => '驾著者',
-'駕著述' => '驾著述',
-'罵著' => '骂着',
-'罵著書' => '骂著书',
-'罵著作' => '骂著作',
-'罵著名' => '骂著名',
-'罵著錄' => '骂著录',
-'罵著稱' => '骂著称',
-'罵著者' => '骂著者',
-'罵著述' => '骂著述',
-'騎著' => '骑着',
-'騎著書' => '骑著书',
-'騎著作' => '骑著作',
-'騎著名' => '骑著名',
-'騎著錄' => '骑著录',
-'騎著稱' => '骑著称',
-'騎著者' => '骑著者',
-'騎著述' => '骑著述',
-'騙著' => '骗着',
-'騙著書' => '骗著书',
-'騙著作' => '骗著作',
-'騙著名' => '骗著名',
-'騙著錄' => '骗著录',
-'騙著稱' => '骗著称',
-'騙著者' => '骗著者',
-'騙著述' => '骗著述',
-'高著' => '高着',
-'高著书' => '高著书',
-'高著書' => '高著书',
-'高著作' => '高著作',
-'高著名' => '高著名',
-'高著录' => '高著录',
-'高著錄' => '高著录',
-'高著稱' => '高著称',
-'高著称' => '高著称',
-'高著者' => '高著者',
-'高著述' => '高著述',
-'髭著' => '髭着',
-'髭著书' => '髭著书',
-'髭著書' => '髭著书',
-'髭著作' => '髭著作',
-'髭著名' => '髭著名',
-'髭著錄' => '髭著录',
-'髭著录' => '髭著录',
-'髭著称' => '髭著称',
-'髭著稱' => '髭著称',
-'髭著者' => '髭著者',
-'髭著述' => '髭著述',
+'餘年無多' => '馀年无多',
+'餘慶' => '馀庆',
+'餘瀋' => '馀渖',
+'馬德鍾' => '马德钟',
+'高昇' => '高升',
+'高陞' => '高升',
'鬱姓' => '鬱姓',
'鬱氏' => '鬱氏',
'魏徵' => '魏徵',
'魚乾乾' => '鱼干干',
-'麯崇裕' => '麯崇裕',
-'麴義' => '麴义',
-'麴义' => '麴义',
-'麴英' => '麴英',
+'鳴鍾' => '鸣钟',
'麽氏' => '麽氏',
'麽麽' => '麽麽',
'麼麼' => '麽麽',
+'黃麴毒素' => '黄曲毒素',
'黄润乾' => '黄润乾',
'黃潤乾' => '黄润乾',
-'黏著' => '黏着',
-'黏著书' => '黏著书',
-'黏著書' => '黏著书',
-'黏著作' => '黏著作',
-'黏著名' => '黏著名',
-'黏著录' => '黏著录',
-'黏著錄' => '黏著录',
-'黏著称' => '黏著称',
-'黏著稱' => '黏著称',
-'黏著者' => '黏著者',
-'黏著述' => '黏著述',
+'黃鍾' => '黄钟',
+'龍鍾' => '龙钟',
+',陞' => ',升',
);
$zh2TW = array(
-'“' => '「',
-'”' => '」',
-'‘' => '『',
-'’' => '』',
-'三極管' => '三極體',
+'0杆' => '0桿',
+'1杆' => '1桿',
+'2杆' => '2桿',
+'3杆' => '3桿',
+'4杆' => '4桿',
+'5杆' => '5桿',
+'6杆' => '6桿',
+'7杆' => '7桿',
+'8杆' => '8桿',
+'9杆' => '9桿',
+'甲型肝炎' => 'A型肝炎',
+'甲肝' => 'A肝',
+'乙型肝炎' => 'B型肝炎',
+'乙肝' => 'B肝',
+'丙型肝炎' => 'C型肝炎',
+'丙肝' => 'C肝',
+'IP地址' => 'IP位址',
+'乔戈里峰' => 'K2',
+'·威爾士' => '·威爾士',
+'·威尔士' => '·威爾士',
+'一杆' => '一桿',
+'七杆' => '七桿',
+'三杆' => '三桿',
'三极管' => '三極體',
-'世界裏' => '世界裡',
-'中文裏' => '中文裡',
-'串行' => '串列',
-'串列加速器' => '串列加速器',
+'三極管' => '三極體',
+'达累斯萨拉姆' => '三蘭港',
+'上落客' => '上下客',
+'落車' => '下車',
+'不來梅' => '不萊梅',
+'不来梅' => '不萊梅',
'以太网' => '乙太網',
-'奶酪' => '乳酪',
-'二極管' => '二極體',
+'九杆' => '九桿',
+'了結他' => '了結他',
+'二手煙' => '二手菸',
+'二杆' => '二桿',
'二极管' => '二極體',
-'交互式' => '互動式',
+'二極管' => '二極體',
+'交互设计' => '互動設計',
+'五杆' => '五桿',
'阿塞拜疆' => '亞塞拜然',
+'阿斯旺' => '亞斯文',
'人工智能' => '人工智慧',
-'接口' => '介面',
+'人机交互' => '人機互動',
+'石勒蘇益格' => '什勒斯維希',
+'石勒苏益格' => '什勒斯維希',
+'界面' => '介面',
+'伊利诺伊州' => '伊利諾州',
+'伊斯坦布爾' => '伊斯坦堡',
+'伊斯坦布尔' => '伊斯坦堡',
+'伊斯兰堡' => '伊斯蘭瑪巴德',
+'伊斯蘭堡' => '伊斯蘭瑪巴德',
+'埃博拉' => '伊波拉',
+'伊丽莎白' => '伊莉莎白',
+'掌上壓' => '伏地挺身',
+'俯卧撑' => '伏地挺身',
+'伯明翰' => '伯明罕',
'服务器' => '伺服器',
-'字節' => '位元組',
'字节' => '位元組',
-'作品裏' => '作品裡',
-'信道' => '信道',
-'优先级' => '優先順序',
-'元兇' => '元凶',
-'元凶' => '元凶',
+'字節' => '位元組',
+'佛罗伦萨' => '佛羅倫斯',
+'操作系统' => '作業系統',
+'系数' => '係數',
+'避孕套' => '保險套',
+'傅里叶' => '傅立葉',
'光盘' => '光碟',
'光驱' => '光碟機',
'克羅地亞' => '克羅埃西亞',
'克罗地亚' => '克羅埃西亞',
-'全角' => '全形',
-'冬天裏' => '冬天裡',
-'冬日裏' => '冬日裡',
-'凉菜' => '冷盤',
-'冷菜' => '冷盤',
-'凶器' => '凶器',
-'兇器' => '凶器',
-'凶徒' => '凶徒',
-'兇徒' => '凶徒',
-'兇手' => '凶手',
-'凶手' => '凶手',
-'兇案' => '凶案',
-'凶案' => '凶案',
-'凶殘' => '凶殘',
-'兇殘' => '凶殘',
-'凶残' => '凶殘',
-'兇殺' => '凶殺',
-'凶杀' => '凶殺',
-'凶殺' => '凶殺',
+'克里斯托弗' => '克里斯多福',
+'万维网' => '全球資訊網',
+'八杆' => '八桿',
+'公共交通' => '公共運輸',
+'六杆' => '六桿',
+'凯瑟琳' => '凱薩琳',
+'嘉芙蓮' => '凱薩琳',
'打印' => '列印',
'列支敦士登' => '列支敦斯登',
-'剪彩' => '剪綵',
+'前波美拉尼亚' => '前波莫瑞',
+'前波美拉尼亞' => '前波莫瑞',
'加蓬' => '加彭',
-'总线' => '匯流排',
+'加沙地带' => '加薩走廊',
+'加沙地帶' => '加薩走廊',
+'包豪斯' => '包浩斯',
+'北朝鲜' => '北韓',
'局域网' => '區域網',
+'十杆' => '十桿',
'特立尼達和多巴哥' => '千里達托貝哥',
'特立尼达和托巴哥' => '千里達托貝哥',
-'半角' => '半形',
-'卡塔爾' => '卡達',
+'南朝鲜' => '南韓',
+'卡斯特罗' => '卡斯楚',
'卡塔尔' => '卡達',
-'打印機' => '印表機',
+'卡塔爾' => '卡達',
'打印机' => '印表機',
-'厄立特里亚' => '厄利垂亞',
+'打印機' => '印表機',
'厄立特里亞' => '厄利垂亞',
-'厄瓜多尔' => '厄瓜多',
+'厄立特里亚' => '厄利垂亞',
+'厄利垂亚' => '厄利垂亞',
'厄瓜多爾' => '厄瓜多',
+'厄瓜多' => '厄瓜多',
+'厄瓜多尔' => '厄瓜多',
+'源代码' => '原始碼',
+'圆珠笔' => '原子筆',
+'反煙' => '反菸',
+'可卡因' => '古柯鹼',
+'便携式' => '可攜式',
+'叱咤' => '叱吒',
+'叱咤9' => '叱咤9',
+'叱咤M' => '叱咤M',
+'叱咤叱' => '叱咤叱',
+'叱咤咤' => '叱咤咤',
+'叱咤樂壇' => '叱咤樂壇',
+'斯坦福' => '史丹福',
+'斯皮尔伯格' => '史匹柏',
+'斯特劳斯' => '史特勞斯',
'斯威士兰' => '史瓦濟蘭',
'斯威士蘭' => '史瓦濟蘭',
-'吉布提' => '吉布地',
+'斯蒂芬' => '史蒂芬',
+'斯大林' => '史達林',
+'結他' => '吉他',
+'乞力馬札羅' => '吉力馬札羅',
+'乞力马扎罗' => '吉力馬札羅',
'吉布堤' => '吉布地',
+'吉布提' => '吉布地',
+'吉尔吉斯斯坦' => '吉爾吉斯',
'基里巴斯' => '吉里巴斯',
'圖瓦盧' => '吐瓦魯',
'图瓦卢' => '吐瓦魯',
+'吸煙' => '吸菸',
+'呂宋煙' => '呂宋菸',
'哈萨克斯坦' => '哈薩克',
+'格丁根' => '哥廷根',
+'哥特式' => '哥德式',
'哥斯達黎加' => '哥斯大黎加',
'哥斯达黎加' => '哥斯大黎加',
-'格魯吉亞' => '喬治亞',
-'格鲁吉亚' => '喬治亞',
+'卡拉奇' => '喀拉蚩',
+'乔治·奥威尔' => '喬治·歐威爾',
'佐治亚' => '喬治亞',
'佐治亞' => '喬治亞',
-'嘴裏' => '嘴裡',
+'格魯吉亞' => '喬治亞',
+'格鲁吉亚' => '喬治亞',
+'单反相机' => '單眼相機',
+'單鏡反光機' => '單眼相機',
+'嘯咤' => '嘯吒',
+'四杆' => '四桿',
'土库曼斯坦' => '土庫曼',
-'薯仔' => '土豆',
-'土豆網' => '土豆網',
-'土豆网' => '土豆網',
-'坦桑尼亚' => '坦尚尼亞',
+'圖盧茲' => '土魯斯',
+'图卢兹' => '土魯斯',
+'IP' => '地址',
+'戛纳' => '坎城',
+'堪培拉' => '坎培拉',
'坦桑尼亞' => '坦尚尼亞',
+'坦桑尼亚' => '坦尚尼亞',
'端口' => '埠',
+'首席执行官' => '執行長',
'塔吉克斯坦' => '塔吉克',
-'塞舌尔' => '塞席爾',
'塞舌爾' => '塞席爾',
-'塞浦路斯' => '塞普勒斯',
-'夏天裏' => '夏天裡',
-'夏日裏' => '夏日裡',
-'多明尼加共和國' => '多明尼加',
-'多米尼加共和国' => '多明尼加',
-'多米尼加共和國' => '多明尼加',
-'多米尼加国' => '多米尼克',
-'多明尼加國' => '多米尼克',
-'穿梭機' => '太空梭',
+'塞舌尔' => '塞席爾',
+'萨拉热窝' => '塞拉耶佛',
+'薩拉熱窩' => '塞拉耶佛',
+'塞爾維亞和黑山' => '塞爾維亞與蒙特內哥羅',
+'塞爾維亞與蒙特內哥羅' => '塞爾維亞與蒙特內哥羅',
+'塞尔维亚和黑山' => '塞爾維亞與蒙特內哥羅',
+'塞维利亚' => '塞維亞',
+'西維爾' => '塞維亞',
+'塞黑' => '塞蒙',
+'英联邦' => '大英國協',
+'共和联邦' => '大英國協',
+'英聯邦' => '大英國協',
+'宇航员' => '太空人',
+'太空飛行員' => '太空人',
'航天飞机' => '太空梭',
-'尼日利亚' => '奈及利亞',
+'穿梭機' => '太空梭',
+'宇航服' => '太空衣',
+'航天器' => '太空飛行器',
'尼日利亞' => '奈及利亞',
-'字符' => '字元',
-'字号' => '字型大小',
+'尼日利亚' => '奈及利亞',
+'忌廉' => '奶油',
+'荷里活' => '好萊塢',
+'威廉姆斯' => '威廉士',
+'威斯特法倫' => '威斯伐倫',
+'威斯特法伦' => '威斯伐倫',
+'威士顿康星' => '威斯康辛',
+'威爾士' => '威爾斯',
+'威尔士' => '威爾斯',
'字库' => '字型檔',
-'字符集' => '字符集',
'存盘' => '存檔',
-'學裏' => '學裡',
+'门德尔松' => '孟德爾頌',
+'孟德爾遜' => '孟德爾頌',
+'安哈爾特' => '安哈特',
+'安哈尔特' => '安哈特',
'安提瓜和巴布達' => '安地卡及巴布達',
'安提瓜和巴布达' => '安地卡及巴布達',
-'宋元' => '宋元',
'洪都拉斯' => '宏都拉斯',
-'寻址' => '定址',
-'寒假裏' => '寒假裡',
+'密歇根' => '密西根',
'宽带' => '寬頻',
-'老撾' => '寮國',
+'老撾人民民主共和國' => '寮人民民主共和國',
+'老挝人民民主共和国' => '寮人民民主共和國',
'老挝' => '寮國',
-'打门' => '射門',
-'專輯裏' => '專輯裡',
+'老撾' => '寮國',
+'老挝语' => '寮語',
+'老撾語' => '寮語',
+'高峰时段' => '尖峰時段',
+'高峰时间' => '尖峰時間',
'贊比亞' => '尚比亞',
'赞比亚' => '尚比亞',
-'尼日爾' => '尼日',
+'尼克松' => '尼克森',
'尼日尔' => '尼日',
-'山洞裏' => '山洞裡',
-'巴布亞新畿內亞' => '巴布亞紐幾內亞',
+'尼日爾' => '尼日',
+'雅马哈' => '山葉',
+'机床' => '工具機',
+'機床' => '工具機',
+'珍寶客機' => '巨無霸客機',
+'发达国家' => '已開發國家',
+'巴塞隆拿' => '巴塞隆納',
+'巴塞罗那' => '巴塞隆納',
'巴布亚新几内亚' => '巴布亞紐幾內亞',
+'巴布亞新畿內亞' => '巴布亞紐幾內亞',
+'巴士拉' => '巴斯拉',
'巴巴多斯' => '巴貝多',
-'布基纳法索' => '布吉納法索',
+'佈' => '布',
'布基納法索' => '布吉納法索',
-'布什' => '布希',
+'布基纳法索' => '布吉納法索',
'布殊' => '布希',
+'布什' => '布希',
+'勃兰登堡' => '布蘭登堡',
+'勃蘭登堡' => '布蘭登堡',
+'布里斯托尔' => '布里斯托',
+'布隆方丹' => '布隆泉',
+'希拉里' => '希拉蕊',
+'希特拉' => '希特勒',
+'巴尔米拉环礁' => '帕邁拉環礁',
'帕劳' => '帛琉',
-'例程' => '常式',
-'平治之乱' => '平治之亂',
-'平治之亂' => '平治之亂',
-'年代裏' => '年代裡',
+'希拉克' => '席哈克',
+'账' => '帳',
+'干着急' => '干著急',
+'干着' => '幹著',
+'畿內亞' => '幾內亞',
'几内亚比绍' => '幾內亞比索',
'幾內亞比紹' => '幾內亞比索',
-'彩带' => '彩帶',
-'彩排' => '彩排',
-'彩楼' => '彩樓',
-'彩牌楼' => '彩牌樓',
-'復蘇' => '復甦',
-'复苏' => '復甦',
-'心裏' => '心裡',
+'比利牛斯' => '庇里牛斯',
+'康涅狄格' => '康乃狄克',
+'约翰斯顿岛' => '強斯頓環礁',
+'形而上学' => '形上學',
+'形而上學' => '形上學',
+'得克薩斯' => '德克薩斯',
+'得克萨斯' => '德克薩斯',
+'德累斯頓' => '德勒斯登',
+'德累斯顿' => '德勒斯登',
+'德里达' => '德希達',
+'特拉华' => '德拉瓦',
+'特拉華' => '德拉瓦',
'快闪存储器' => '快閃記憶體',
'闪存' => '快閃記憶體',
'想象' => '想像',
-'传感' => '感測',
-'习用' => '慣用',
-'戏彩娱亲' => '戲綵娛親',
-'戲裏' => '戲裡',
-'手电筒' => '手電筒',
-'手电' => '手電筒',
-'括号' => '括弧',
-'拿破侖' => '拿破崙',
-'拿破仑' => '拿破崙',
+'愛德文' => '愛德溫',
+'艾滋' => '愛滋',
+'艾奧瓦' => '愛荷華',
+'爱德华州' => '愛達荷州',
+'应用程序' => '應用程式',
+'戈爾巴喬夫' => '戈巴契夫',
+'戈尔巴乔夫' => '戈巴契夫',
+'戒煙' => '戒菸',
+'戴克里先' => '戴克里先',
+'抽煙' => '抽菸',
+'拉普兰' => '拉布蘭',
+'拒煙' => '拒菸',
+'捲煙' => '捲菸',
'積架' => '捷豹',
-'扫瞄仪' => '掃瞄器',
-'挂钩' => '掛鉤',
-'掛鈎' => '掛鉤',
'控件' => '控制項',
+'推杆' => '推桿',
+'第比利斯' => '提比里西',
+'揮杆' => '揮桿',
+'挥杆' => '揮桿',
+'搜索引擎' => '搜尋引擎',
+'摩根士丹利' => '摩根史坦利',
'台球' => '撞球',
-'桌球' => '撞球',
-'便携式' => '攜帶型',
-'故事裏' => '故事裡',
+'攻打印' => '攻打印',
+'数字技术' => '數位技術',
+'數碼技術' => '數位技術',
+'數碼相機' => '數位相機',
+'数码相机' => '數位相機',
+'数字信号' => '數位訊號',
+'數碼訊號' => '數位訊號',
+'数字电视' => '數位電視',
+'數碼電視' => '數位電視',
'调制解调器' => '數據機',
'調制解調器' => '數據機',
-'斯洛文尼亞' => '斯洛維尼亞',
'斯洛文尼亚' => '斯洛維尼亞',
-'新纪元' => '新紀元',
-'新紀元' => '新紀元',
-'日子裏' => '日子裡',
-'春假裏' => '春假裡',
-'春天裏' => '春天裡',
-'春日裏' => '春日裡',
-'時間裏' => '時間裡',
-'芯片' => '晶元',
-'暑假裏' => '暑假裡',
-'村子裏' => '村子裡',
+'斯洛文尼亞' => '斯洛維尼亞',
+'新罕布什尔' => '新罕布夏',
+'施罗德' => '施洛德',
+'旱煙' => '旱菸',
+'普利策' => '普利茲',
+'芯片' => '晶片',
+'智能卡' => '智慧卡',
+'智能手機' => '智慧型手機',
+'智能手机' => '智慧型手機',
+'智能电话' => '智慧型電話',
+'智能電話' => '智慧型電話',
+'知识产权' => '智慧財產權',
+'知識產權' => '智慧財產權',
+'萌島' => '曼島',
+'马恩岛' => '曼島',
+'木杆' => '木桿',
+'列奥纳多' => '李奧納多',
+'杜塞爾多夫' => '杜塞道夫',
+'杜塞尔多夫' => '杜塞道夫',
+'迪拜' => '杜拜',
+'亚细安' => '東協',
+'东盟' => '東協',
+'东南亚国家联盟' => '東南亞國協',
+'柏林墙' => '柏林圍牆',
+'柏林牆' => '柏林圍牆',
'乍得' => '查德',
-'克林頓' => '柯林頓',
+'查韦斯' => '查維茲',
'克林顿' => '柯林頓',
-'格林納達' => '格瑞那達',
+'克林頓' => '柯林頓',
+'撒切尔' => '柴契爾',
+'戴卓爾' => '柴契爾',
'格林纳达' => '格瑞那達',
-'凡高' => '梵谷',
-'森林裏' => '森林裡',
-'棺材裏' => '棺材裡',
-'榴蓮' => '榴槤',
+'格林納達' => '格瑞那達',
+'乒乓球' => '桌球',
+'乒乓' => '桌球',
+'杆弟' => '桿弟',
+'杆身' => '桿身',
+'杆頭' => '桿頭',
+'杆头' => '桿頭',
+'梅尔·吉布森' => '梅爾·吉勃遜',
+'梵高' => '梵谷',
+'桑巴舞' => '森巴舞',
'榴莲' => '榴槤',
-'仿真' => '模擬',
-'毛里裘斯' => '模里西斯',
+'榴蓮' => '榴槤',
+'枪支' => '槍枝',
+'标准杆' => '標準桿',
+'標準杆' => '標準桿',
'毛里求斯' => '模里西斯',
+'毛里裘斯' => '模里西斯',
'機械人' => '機器人',
'机器人' => '機器人',
+'概率' => '機率',
+'電單車' => '機車',
+'枱' => '檯',
'字段' => '欄位',
-'歷史裏' => '歷史裡',
-'元音' => '母音',
-'永历' => '永曆',
+'奧巴馬' => '歐巴馬',
+'奥巴马' => '歐巴馬',
+'正在叱咤' => '正在叱咤',
'文莱' => '汶萊',
+'沙律' => '沙拉',
'沙特阿拉伯' => '沙烏地阿拉伯',
'沙地阿拉伯' => '沙烏地阿拉伯',
+'法属圭亚那' => '法屬蓋亞那',
+'波斯尼亚' => '波士尼亞',
+'波斯尼亞' => '波士尼亞',
'波斯尼亞黑塞哥維那' => '波士尼亞赫塞哥維納',
'波斯尼亚和黑塞哥维那' => '波士尼亞赫塞哥維納',
'博茨瓦纳' => '波札那',
'博茨瓦納' => '波札那',
-'流程控制' => '流程控制',
+'波黑' => '波赫',
+'洋煙' => '洋菸',
+'帕特里克' => '派屈克',
+'海洛英' => '海洛因',
'侯赛因' => '海珊',
'侯賽因' => '海珊',
-'深淵裏' => '深淵裡',
-'光标' => '游標',
'鼠标' => '滑鼠',
-'算法' => '演算法',
+'漢诺威' => '漢諾瓦',
+'汉诺威' => '漢諾瓦',
'乌兹别克斯坦' => '烏茲別克',
-'词组' => '片語',
-'獄裏' => '獄裡',
+'烤煙' => '烤菸',
+'無煙日' => '無菸日',
+'無煙環境' => '無菸環境',
+'烟熏' => '煙燻',
+'首席运营官' => '營運長',
+'熏烤' => '燻烤',
+'熏肉' => '燻肉',
+'熏黑' => '燻黑',
+'版权信息' => '版權資訊',
+'疯牛症' => '狂牛症',
+'鐵托' => '狄托',
+'铁托' => '狄托',
'塞拉利昂' => '獅子山',
-'危地马拉' => '瓜地馬拉',
+'独联体' => '獨立國協',
+'独立国家联合体' => '獨立國家國協',
+'波利尼西亚' => '玻里尼西亞',
+'波利尼西亞' => '玻里尼西亞',
+'本杰明' => '班傑明',
+'本傑明' => '班傑明',
+'球杆' => '球桿',
+'理查德' => '理察',
+'卢塞恩' => '琉森',
'危地馬拉' => '瓜地馬拉',
+'危地马拉' => '瓜地馬拉',
+'巴伦西亚' => '瓦倫西亞',
+'華倫西亞' => '瓦倫西亞',
'冈比亚' => '甘比亞',
'岡比亞' => '甘比亞',
-'疑兇' => '疑凶',
-'疑凶' => '疑凶',
-'百科裏' => '百科裡',
-'皮裏陽秋' => '皮裡陽秋',
+'肯尼迪' => '甘迺迪',
+'留尼汪' => '留尼旺',
+'毕加索' => '畢卡索',
+'迭代' => '疊代',
+'徵狀' => '症狀',
+'勃朗宁' => '白朗寧',
+'百慕大' => '百慕達',
'盧旺達' => '盧安達',
'卢旺达' => '盧安達',
-'真凶' => '真凶',
-'真兇' => '真凶',
-'眼睛裏' => '眼睛裡',
-'硅片' => '矽片',
-'硅谷' => '矽谷',
+'睾' => '睪',
+'知识产权局' => '知識產權局',
+'知識產權署' => '知識產權署',
+'知識產權局' => '知識產權署',
+'知识产权署' => '知識產權署',
+'硅' => '矽',
+'硅藻' => '硅藻',
'硬盘' => '硬碟',
'硬件' => '硬體',
'盘片' => '碟片',
'磁盘' => '磁碟',
'磁道' => '磁軌',
-'秋假裏' => '秋假裡',
-'秋天裏' => '秋天裡',
-'秋日裏' => '秋日裡',
-'程控' => '程式控制',
+'禁煙' => '禁菸',
+'福尔马林' => '福馬林',
+'福爾馬林' => '福馬林',
+'私煙' => '私菸',
+'程序员' => '程式設計師',
+'编程语言' => '程式語言',
+'空氣質素' => '空氣品質',
+'空气质量' => '空氣品質',
'突尼斯' => '突尼西亞',
-'尾注' => '章節附註',
'蹦极跳' => '笨豬跳',
'绑紧跳' => '笨豬跳',
-'等于' => '等於',
-'短訊' => '簡訊',
'短信' => '簡訊',
-'系列裏' => '系列裡',
-'新西蘭' => '紐西蘭',
+'纽黑文' => '紐哈芬',
+'新奥尔良' => '紐奧良',
+'新奧爾良' => '紐奧良',
+'新几内亚' => '紐幾內亞',
'新西兰' => '紐西蘭',
-'所罗门群岛' => '索羅門群島',
+'新西蘭' => '紐西蘭',
+'紙煙' => '紙菸',
+'索贊尼辛' => '索忍尼辛',
+'索尔仁尼琴' => '索忍尼辛',
'所羅門群島' => '索羅門群島',
-'索馬里' => '索馬利亞',
+'所罗门群岛' => '索羅門群島',
'索马里' => '索馬利亞',
-'结彩' => '結綵',
+'索馬里' => '索馬利亞',
+'索马里兰' => '索馬利蘭',
+'索馬里蘭' => '索馬利蘭',
+'維爾京群島' => '維京群島',
+'维尔京群岛' => '維京群島',
+'弗吉尼亚' => '維吉尼亞',
'佛得角' => '維德角',
-'網絡' => '網路',
-'网络' => '網路',
-'互聯網' => '網際網路',
+'维特根斯坦' => '維根斯坦',
+'互联网络' => '網際網路',
'因特网' => '網際網路',
-'彩球' => '綵球',
-'彩绸' => '綵綢',
-'彩线' => '綵線',
-'彩船' => '綵船',
-'彩衣' => '綵衣',
-'綫' => '線',
-'缉凶' => '緝凶',
-'緝兇' => '緝凶',
-'緝凶' => '緝凶',
+'互聯網' => '網際網路',
+'互联网' => '網際網路',
+'系着' => '繫著',
+'卢瓦尔' => '羅亞爾',
+'盧瓦爾' => '羅亞爾',
+'卢浮宫' => '羅浮宮',
+'樂行童軍' => '羅浮童軍',
'意大利' => '義大利',
-'老字号' => '老字號',
+'昂山素姬' => '翁山蘇姬',
+'昂山素季' => '翁山蘇姬',
'圣基茨和尼维斯' => '聖克里斯多福及尼維斯',
'聖吉斯納域斯' => '聖克里斯多福及尼維斯',
'聖文森特和格林納丁斯' => '聖文森及格瑞那丁',
'圣文森特和格林纳丁斯' => '聖文森及格瑞那丁',
-'圣卢西亚' => '聖露西亞',
+'圣赫勒拿' => '聖赫倫那',
'聖盧西亞' => '聖露西亞',
+'圣卢西亚' => '聖露西亞',
'圣马力诺' => '聖馬利諾',
'聖馬力諾' => '聖馬利諾',
-'肚裏' => '肚裡',
'肯尼亚' => '肯亞',
-'肯雅' => '肯亞',
-'航天大学' => '航天大學',
-'苦裏' => '苦裡',
-'毛里塔尼亚' => '茅利塔尼亞',
+'氨基酸' => '胺基酸',
+'卧' => '臥',
+'自由泳' => '自由式',
+'三藩市' => '舊金山',
+'艾森豪威尔' => '艾森豪',
+'埃菲尔' => '艾菲爾',
+'阿里埃勒·沙龙' => '艾里爾·夏隆',
+'阿里埃勒·沙龍' => '艾里爾·夏隆',
+'帕塔亚' => '芭達亞',
+'黎克特制' => '芮氏',
+'里氏0' => '芮氏0',
+'里氏1' => '芮氏1',
+'里氏2' => '芮氏2',
+'里氏3' => '芮氏3',
+'里氏4' => '芮氏4',
+'里氏5' => '芮氏5',
+'里氏6' => '芮氏6',
+'里氏7' => '芮氏7',
+'里氏8' => '芮氏8',
+'里氏9' => '芮氏9',
+'里氏地震规模' => '芮氏地震規模',
+'里氏规模' => '芮氏規模',
+'里氏震级' => '芮氏規模',
+'当且仅当' => '若且唯若',
+'味美思' => '苦艾酒',
'毛里塔尼亞' => '茅利塔尼亞',
+'毛里塔尼亚' => '茅利塔尼亞',
+'霍尔木兹' => '荷姆茲',
+'霍爾木茲' => '荷姆茲',
+'荷李活道' => '荷李活道',
'莫桑比克' => '莫三比克',
-'万历' => '萬曆',
-'瓦努阿图' => '萬那杜',
+'瓦文萨' => '華勒沙',
+'華里沙' => '華勒沙',
+'瓦格纳' => '華格納',
+'煙具' => '菸具',
+'煙品' => '菸品',
+'煙嘴' => '菸嘴',
+'煙捲' => '菸捲',
+'煙斗' => '菸斗',
+'煙民' => '菸民',
+'煙灰' => '菸灰',
+'煙癮' => '菸癮',
+'煙絲' => '菸絲',
+'煙草' => '菸草',
+'煙葉' => '菸葉',
+'煙蒂' => '菸蒂',
+'煙袋' => '菸袋',
+'煙農' => '菸農',
+'煙酒' => '菸酒',
+'煙頭' => '菸頭',
+'煙鬼' => '菸鬼',
+'煙鹼' => '菸鹼',
+'万历朝鲜战争' => '萬曆朝鮮戰爭',
'瓦努阿圖' => '萬那杜',
-'也门' => '葉門',
+'瓦努阿图' => '萬那杜',
+'叶利钦' => '葉爾欽',
+'葉利欽' => '葉爾欽',
+'埃里温' => '葉里溫',
+'埃里溫' => '葉里溫',
'也門' => '葉門',
+'也门' => '葉門',
'着' => '著',
-'科摩羅' => '葛摩',
'科摩罗' => '葛摩',
+'科摩羅' => '葛摩',
+'黑山共和國' => '蒙特內哥羅共和國',
+'黑山共和国' => '蒙特內哥羅共和國',
+'蒙特利爾' => '蒙特婁',
+'滿地可' => '蒙特婁',
+'蒙特利尔' => '蒙特婁',
+'普密蓬' => '蒲美蓬',
'布隆迪' => '蒲隆地',
-'圭亞那' => '蓋亞那',
'圭亚那' => '蓋亞那',
-'火锅盖帽' => '蓋火鍋',
+'开曼群岛' => '蓋曼群島',
+'開曼群島' => '蓋曼群島',
+'蕭士達高維契' => '蕭士塔高維奇',
+'肖斯塔科维奇' => '蕭士塔高維奇',
+'肖邦' => '蕭邦',
+'薛定谔' => '薛丁格',
+'扎伊爾' => '薩伊',
+'扎伊尔' => '薩伊',
+'素檀' => '蘇丹',
'苏里南' => '蘇利南',
-'行凶' => '行凶',
-'行兇' => '行凶',
-'行凶后' => '行凶後',
-'行兇後' => '行凶後',
-'行凶後' => '行凶後',
-'流動電話' => '行動電話',
+'浮罗交怡' => '蘭卡威',
+'浮羅交怡' => '蘭卡威',
+'劳拉' => '蘿拉',
+'荧光' => '螢光',
+'荧屏' => '螢屏',
+'屏幕' => '螢幕',
+'流動網絡' => '行動網路',
+'移动网络' => '行動網路',
'移动电话' => '行動電話',
-'行程控制' => '行程控制',
-'衞' => '衛',
-'卫生' => '衛生',
-'衞生' => '衛生',
-'埃塞俄比亚' => '衣索比亞',
+'流動電話' => '行動電話',
+'冲着' => '衝著',
'埃塞俄比亞' => '衣索比亞',
-'裏勾外連' => '裡勾外連',
-'裏面' => '裡面',
+'埃塞俄比亚' => '衣索比亞',
+'克隆人' => '複製人',
+'國際象棋' => '西洋棋',
+'囯际象棋' => '西洋棋',
+'国际象棋' => '西洋棋',
+'赫梯' => '西臺',
+'解像度' => '解析度',
'分辨率' => '解析度',
'译码' => '解碼',
'出租车' => '計程車',
-'权限' => '許可權',
+'约翰逊' => '詹森',
+'诺曼底' => '諾曼第',
'瑙鲁' => '諾魯',
'瑙魯' => '諾魯',
-'变量' => '變數',
'科特迪瓦' => '象牙海岸',
-'貝寧' => '貝南',
-'贝宁' => '貝南',
-'伯利茲' => '貝里斯',
+'贝尔格莱德' => '貝爾格勒',
+'貝爾格萊德' => '貝爾格勒',
'伯利兹' => '貝里斯',
-'買兇' => '買凶',
-'买凶' => '買凶',
-'買凶' => '買凶',
+'伯利茲' => '貝里斯',
+'首席财务官' => '財務長',
+'集装箱' => '貨櫃',
+'數據庫' => '資料庫',
'数据库' => '資料庫',
'信息论' => '資訊理論',
-'奔驰' => '賓士',
-'平治' => '賓士',
-'利比里亚' => '賴比瑞亞',
+'宾西法尼亚' => '賓夕法尼亞',
'利比里亞' => '賴比瑞亞',
-'萊索托' => '賴索托',
+'利比里亚' => '賴比瑞亞',
'莱索托' => '賴索托',
+'萊索托' => '賴索托',
+'塞浦路斯' => '賽普勒斯',
+'碧咸' => '贝克漢',
+'赫丘勒·波洛' => '赫丘勒·白羅',
+'赫鲁晓夫' => '赫魯雪夫',
+'切尔诺贝利' => '車諾比',
'软驱' => '軟碟機',
'軟件' => '軟體',
'软件' => '軟體',
-'加载' => '載入',
'津巴布韦' => '辛巴威',
'津巴布韋' => '辛巴威',
-'词汇' => '辭彙',
-'加纳' => '迦納',
-'加納' => '迦納',
-'追凶' => '追凶',
-'追兇' => '追凶',
-'這裏' => '這裡',
-'逞凶鬥狠' => '逞凶鬥狠',
-'逞兇鬥狠' => '逞凶鬥狠',
-'逞凶斗狠' => '逞凶鬥狠',
-'即食麵' => '速食麵',
-'方便面' => '速食麵',
-'快速面' => '速食麵',
-'连字号' => '連字號',
+'径入' => '逕入',
+'径到' => '逕到',
+'径取' => '逕取',
+'径启' => '逕啟',
+'径寄' => '逕寄',
+'径庭' => '逕庭',
+'径往' => '逕往',
+'径自' => '逕自',
+'径行' => '逕行',
+'径迎' => '逕迎',
+'链接' => '連結',
+'連結他' => '連結他',
'进制' => '進位',
-'入球' => '進球',
'算子' => '運算元',
-'遠程控制' => '遠程控制',
-'远程控制' => '遠程控制',
+'达·芬奇' => '達·文西',
+'达芬奇' => '達文西',
'溫納圖萬' => '那杜',
-'醫院裏' => '醫院裡',
+'丘吉尔' => '邱吉爾',
+'多普勒' => '都卜勒',
+'奥斯曼' => '鄂圖曼',
'酰' => '醯',
+'里士满' => '里奇蒙',
+'金沙薩' => '金夏沙',
+'金沙萨' => '金夏沙',
+'健力士世界纪录' => '金氏世界紀錄',
+'健力士世界紀錄' => '金氏世界紀錄',
+'吉尼斯世界纪录' => '金氏世界紀錄',
+'钚' => '鈽',
'钩' => '鉤',
'鈎' => '鉤',
-'钩心斗角' => '鉤心鬥角',
-'鈎心鬥角' => '鉤心鬥角',
'锎' => '鉲',
+'锫' => '鉳',
+'镅' => '鋂',
+'镎' => '錼',
+'钫' => '鍅',
+'炼金' => '鍊金',
+'锻炼' => '鍛鍊',
+'锝' => '鎝',
+'鐵杆' => '鐵桿',
+'铁杆' => '鐵桿',
+'泰坦尼克号' => '鐵達尼號',
+'锿' => '鑀',
+'关系着' => '關係著',
'写保护' => '防寫',
+'阿布扎比' => '阿布達比',
'阿拉伯联合酋长国' => '阿拉伯聯合大公國',
'阿拉伯聯合酋長國' => '阿拉伯聯合大公國',
-'噪声' => '雜訊',
-'脱机' => '離線',
-'雪裏紅' => '雪裡紅',
-'雪裏蕻' => '雪裡蕻',
-'雪铁龙' => '雪鐵龍',
-'青霉素' => '青黴素',
-'异步' => '非同步',
+'亚拉巴马' => '阿拉巴馬',
+'阿联酋' => '阿聯',
+'阿聯酋' => '阿聯',
+'罗纳德·里根' => '隆納·雷根',
+'私隱' => '隱私',
+'耶加達' => '雅加達',
+'雅尔塔' => '雅爾達',
+'雅爾塔' => '雅爾達',
+'雅穆苏克雷' => '雅穆索戈',
+'雅穆蘇克雷' => '雅穆索戈',
+'悉尼' => '雪梨',
+'雪茄煙' => '雪茄菸',
+'莱特湾' => '雷伊泰灣',
+'萊特灣' => '雷伊泰灣',
+'激光' => '雷射',
+'雷诺阿' => '雷諾瓦',
+'電子煙' => '電子菸',
+'晶體管' => '電晶體',
+'晶体管' => '電晶體',
+'电杆' => '電桿',
+'电线杆' => '電線桿',
+'计算机程序' => '電腦程式',
+'电脑程序' => '電腦程式',
+'荷尔斯泰因' => '霍爾斯坦',
+'荷爾斯泰因' => '霍爾斯坦',
+'朝鲜战争' => '韓戰',
'声卡' => '音效卡',
'缺省' => '預設',
-'颁布' => '頒布',
-'頒佈' => '頒布',
-'領域裏' => '領域裡',
-'头球' => '頭槌',
-'粒入球' => '顆進球',
-'館裏' => '館裡',
+'导弹' => '飛彈',
+'糊口' => '餬口',
+'香煙' => '香菸',
'马里共和国' => '馬利共和國',
'馬里共和國' => '馬利共和國',
+'马拉维' => '馬拉威',
+'馬斯特里赫特' => '馬斯垂克',
+'马斯特里赫特' => '馬斯垂克',
'马耳他' => '馬爾他',
-'马尔代夫' => '馬爾地夫',
'馬爾代夫' => '馬爾地夫',
-'萬事得' => '馬自達',
-'狄安娜' => '黛安娜',
+'马尔代夫' => '馬爾地夫',
+'馬利蘭' => '馬里蘭',
+'高清电视' => '高畫質電視',
+'斗着' => '鬥著',
+'魯賓斯·巴里切羅' => '魯本·巴瑞切羅',
+'咪高峰' => '麥克風',
+'迈克尔' => '麥可',
+'麦克尔' => '麥可',
+'邁凱輪' => '麥拿輪',
+'迈凯轮' => '麥拿輪',
+'马萨诸塞' => '麻薩諸塞',
'戴安娜' => '黛安娜',
-'點裏' => '點裡',
-'位图' => '點陣圖',
+'狄安娜' => '黛安娜',
+'點煙' => '點菸',
+'霉素' => '黴素',
);
$zh2HK = array(
-'505線' => '505綫',
-'505线' => '505綫',
-'507線' => '507綫',
-'507线' => '507綫',
-'610線' => '610綫',
-'610线' => '610綫',
-'614P線' => '614P綫',
-'614P线' => '614P綫',
-'614线' => '614綫',
-'614線' => '614綫',
-'615P線' => '615P綫',
-'615P线' => '615P綫',
-'615线' => '615綫',
-'615線' => '615綫',
-'705线' => '705綫',
-'705線' => '705綫',
-'706线' => '706綫',
-'706線' => '706綫',
-'751P線' => '751P綫',
-'751P线' => '751P綫',
-'751線' => '751綫',
-'751线' => '751綫',
-'761P线' => '761P綫',
-'761P線' => '761P綫',
-'“' => '「',
-'”' => '」',
-'‘' => '『',
-'’' => '』',
+'IP地址' => 'IP位址',
+'·威爾士' => '·威爾士',
+'·威尔士' => '·威爾士',
+'一地里' => '一地裏',
+'一年里' => '一年裏',
+'三十六著' => '三十六着',
'三極體' => '三極管',
+'旧金山' => '三藩市',
+'舊金山' => '三藩市',
+'下著' => '下着',
+'下著作' => '下著作',
+'下著名' => '下著名',
+'下著有' => '下著有',
+'下著称' => '下著稱',
+'下著稱' => '下著稱',
+'下著者' => '下著者',
+'下著述' => '下著述',
+'下著录' => '下著錄',
+'下著錄' => '下著錄',
+'不占' => '不佔',
+'不萊梅' => '不來梅',
'不著痕跡' => '不着痕跡',
'不著邊際' => '不着邊際',
-'世界裡' => '世界裏',
-'世界里' => '世界裏',
+'世纪里' => '世紀裏',
+'C型肝炎' => '丙型肝炎',
+'C肝' => '丙肝',
+'并发布' => '並發佈',
'中文里' => '中文裏',
-'中文裡' => '中文裏',
-'民乐' => '中樂',
-'华乐' => '中樂',
-'查德' => '乍得',
'乘著' => '乘着',
'乘著作' => '乘著作',
'乘著名' => '乘著名',
@@ -16551,10 +14594,15 @@ $zh2HK = array(
'乘著者' => '乘著者',
'乘著述' => '乘著述',
'乘著錄' => '乘著錄',
+'B型肝炎' => '乙型肝炎',
+'B肝' => '乙肝',
+'吉力馬札羅' => '乞力馬札羅',
'葉門' => '也門',
+'事里' => '事裏',
'二極體' => '二極管',
-'網際網路' => '互聯網',
'因特网' => '互聯網',
+'網際網路' => '互聯網',
+'井里' => '井裏',
'亮著' => '亮着',
'亮著作' => '亮著作',
'亮著名' => '亮著名',
@@ -16564,6 +14612,7 @@ $zh2HK = array(
'亮著述' => '亮著述',
'亮著錄' => '亮著錄',
'人工智慧' => '人工智能',
+'人数里' => '人數裏',
'仗著' => '仗着',
'仗著作' => '仗著作',
'仗著名' => '仗著名',
@@ -16580,7 +14629,11 @@ $zh2HK = array(
'代表著者' => '代表著者',
'代表著述' => '代表著述',
'代表著錄' => '代表著錄',
+'伊斯坦堡' => '伊斯坦布爾',
+'伊斯蘭瑪巴德' => '伊斯蘭堡',
+'埃博拉' => '伊波拉',
'貝里斯' => '伯利茲',
+'伯明罕' => '伯明翰',
'伴著' => '伴着',
'伴著作' => '伴著作',
'伴著名' => '伴著名',
@@ -16589,6 +14642,40 @@ $zh2HK = array(
'伴著者' => '伴著者',
'伴著述' => '伴著述',
'伴著錄' => '伴著錄',
+'布下了' => '佈下了',
+'布下的' => '佈下的',
+'布光' => '佈光',
+'布告' => '佈告',
+'布局' => '佈局',
+'布展' => '佈展',
+'布控' => '佈控',
+'布于' => '佈於',
+'布於' => '佈於',
+'布施' => '佈施',
+'布景' => '佈景',
+'布满' => '佈滿',
+'布滿' => '佈滿',
+'布置' => '佈置',
+'布设' => '佈設',
+'布設' => '佈設',
+'布警' => '佈警',
+'布道' => '佈道',
+'布防' => '佈防',
+'布阵' => '佈陣',
+'布陣' => '佈陣',
+'布雷、' => '佈雷、',
+'布雷。' => '佈雷。',
+'布雷封锁' => '佈雷封鎖',
+'布雷封鎖' => '佈雷封鎖',
+'布雷的' => '佈雷的',
+'布雷艇' => '佈雷艇',
+'布雷艦' => '佈雷艦',
+'布雷舰' => '佈雷艦',
+'布雷速度' => '佈雷速度',
+'布雷,' => '佈雷,',
+'布雷;' => '佈雷;',
+'布点' => '佈點',
+'布點' => '佈點',
'字節' => '位元組',
'字节' => '位元組',
'低著' => '低着',
@@ -16607,8 +14694,189 @@ $zh2HK = array(
'住著者' => '住著者',
'住著述' => '住著述',
'住著錄' => '住著錄',
+'占0' => '佔0',
+'占1' => '佔1',
+'占2' => '佔2',
+'占3' => '佔3',
+'占4' => '佔4',
+'占5' => '佔5',
+'占6' => '佔6',
+'占7' => '佔7',
+'占8' => '佔8',
+'占9' => '佔9',
+'占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',
+'占〇' => '佔〇',
+'占一' => '佔一',
+'占七' => '佔七',
+'占三' => '佔三',
+'占上風' => '佔上風',
+'占上风' => '佔上風',
+'占下' => '佔下',
+'占下风' => '佔下風',
+'占下風' => '佔下風',
+'占不占' => '佔不佔',
+'占不足' => '佔不足',
+'占世界' => '佔世界',
+'占中' => '佔中',
+'占主' => '佔主',
+'占主要' => '佔主要',
+'占九' => '佔九',
+'占了' => '佔了',
+'占二' => '佔二',
+'占五' => '佔五',
+'占人便宜' => '佔人便宜',
+'占位' => '佔位',
+'占住' => '佔住',
+'占占' => '佔佔',
+'占便宜' => '佔便宜',
+'占俄' => '佔俄',
+'占个' => '佔個',
+'占個' => '佔個',
+'占个位' => '佔個位',
+'占個位' => '佔個位',
+'占億' => '佔億',
+'占亿' => '佔億',
+'占優' => '佔優',
+'占优' => '佔優',
+'占先' => '佔先',
+'占光' => '佔光',
+'占全' => '佔全',
+'占两' => '佔兩',
+'占兩' => '佔兩',
+'占八' => '佔八',
+'占六' => '佔六',
+'占分' => '佔分',
+'占到' => '佔到',
+'占加' => '佔加',
+'占劣' => '佔劣',
+'占北' => '佔北',
+'占十' => '佔十',
+'占千' => '佔千',
+'占半' => '佔半',
+'占南' => '佔南',
+'占印' => '佔印',
+'占去' => '佔去',
+'占取' => '佔取',
+'占台' => '佔台',
+'占囁' => '佔囁',
+'占四' => '佔四',
+'占国' => '佔國',
+'占國' => '佔國',
+'占在' => '佔在',
+'占地' => '佔地',
+'占場' => '佔場',
+'占场' => '佔場',
+'占压' => '佔壓',
+'占壓' => '佔壓',
+'占多' => '佔多',
+'占大' => '佔大',
+'占好' => '佔好',
+'占小' => '佔小',
+'占少' => '佔少',
+'占局部' => '佔局部',
+'占屋' => '佔屋',
+'占山为' => '佔山為',
+'占山為' => '佔山為',
+'占市' => '佔市',
+'占平均' => '佔平均',
+'占床' => '佔床',
+'占座' => '佔座',
+'占後' => '佔後',
+'占得' => '佔得',
+'占德' => '佔德',
+'占所有' => '佔所有',
+'占掉' => '佔掉',
+'占據' => '佔據',
+'占据' => '佔據',
+'占整' => '佔整',
+'占新' => '佔新',
+'占有' => '佔有',
+'占东' => '佔東',
+'占東' => '佔東',
+'占查' => '佔查',
+'占次' => '佔次',
+'占比' => '佔比',
+'占法' => '佔法',
+'占满' => '佔滿',
+'占滿' => '佔滿',
+'占澳' => '佔澳',
+'占為' => '佔為',
+'占为' => '佔為',
+'占率' => '佔率',
+'占用' => '佔用',
+'占畢' => '佔畢',
+'占毕' => '佔畢',
+'占百' => '佔百',
+'占盡' => '佔盡',
+'占尽' => '佔盡',
+'占着' => '佔着',
+'占著' => '佔着',
+'占网' => '佔網',
+'占網' => '佔網',
+'占線' => '佔線',
+'占线' => '佔線',
+'占總' => '佔總',
+'占总' => '佔總',
+'占缺' => '佔缺',
+'占美' => '佔美',
+'占耕' => '佔耕',
+'占至多' => '佔至多',
+'占至少' => '佔至少',
+'占臺' => '佔臺',
+'占英' => '佔英',
+'占萬' => '佔萬',
+'占万' => '佔萬',
+'占著名' => '佔著名',
+'占著者' => '佔著者',
+'占葡' => '佔葡',
+'占蘇' => '佔蘇',
+'占苏' => '佔蘇',
+'占西' => '佔西',
+'占資' => '佔資',
+'占资' => '佔資',
+'占起' => '佔起',
+'占超过' => '佔超過',
+'占超過' => '佔超過',
+'占過' => '佔過',
+'占过' => '佔過',
+'占道' => '佔道',
+'占零' => '佔零',
+'占領' => '佔領',
+'占领' => '佔領',
+'占头' => '佔頭',
+'占頭' => '佔頭',
+'占头筹' => '佔頭籌',
+'占頭籌' => '佔頭籌',
+'占香' => '佔香',
+'占馬' => '佔馬',
+'占马' => '佔馬',
+'占高枝' => '佔高枝',
'維德角' => '佛得角',
-'作品裡' => '作品裏',
'作品里' => '作品裏',
'來著' => '來着',
'來著作' => '來著作',
@@ -16618,7 +14886,8 @@ $zh2HK = array(
'來著者' => '來著者',
'來著述' => '來著述',
'來著錄' => '來著錄',
-'海珊' => '侯賽因',
+'侵占' => '侵佔',
+'俄占' => '俄佔',
'保障著' => '保障着',
'保障著作' => '保障著作',
'保障著名' => '保障著名',
@@ -16635,6 +14904,8 @@ $zh2HK = array(
'信著者' => '信著者',
'信著述' => '信著述',
'信著錄' => '信著錄',
+'个里' => '個裏',
+'倒楣' => '倒霉',
'候著' => '候着',
'候著作' => '候著作',
'候著名' => '候著名',
@@ -16651,6 +14922,7 @@ $zh2HK = array(
'借著者' => '借著者',
'借著述' => '借著述',
'借著錄' => '借著錄',
+'假里' => '假裏',
'做著' => '做着',
'做著作' => '做著作',
'做著名' => '做著名',
@@ -16659,6 +14931,8 @@ $zh2HK = array(
'做著者' => '做著者',
'做著述' => '做著述',
'做著錄' => '做著錄',
+'金氏世界紀錄' => '健力士世界紀錄',
+'吉尼斯世界纪录' => '健力士世界紀錄',
'側著' => '側着',
'側著作' => '側著作',
'側著名' => '側著名',
@@ -16683,10 +14957,15 @@ $zh2HK = array(
'備著者' => '備著者',
'備著述' => '備著述',
'備著錄' => '備著錄',
+'傻里傻气' => '傻裏傻氣',
+'雇员' => '僱員',
+'雇用' => '僱用',
+'凶惡' => '兇惡',
'凶殘' => '兇殘',
'凶殺' => '兇殺',
-'雪铁龙' => '先進',
+'先占' => '先佔',
'雪鐵龍' => '先進',
+'雪铁龙' => '先進',
'光著' => '光着',
'光著作' => '光著作',
'光著名' => '光著名',
@@ -16697,7 +14976,7 @@ $zh2HK = array(
'光著錄' => '光著錄',
'柯林頓' => '克林頓',
'克羅埃西亞' => '克羅地亞',
-'公車上書' => '公車上書',
+'公布' => '公佈',
'冀著' => '冀着',
'冀著作' => '冀著作',
'冀著名' => '冀著名',
@@ -16714,15 +14993,18 @@ $zh2HK = array(
'冒著者' => '冒著者',
'冒著述' => '冒著述',
'冒著錄' => '冒著錄',
-'冬天里' => '冬天裏',
-'冬天裡' => '冬天裏',
-'冬日裡' => '冬日裏',
-'冬日里' => '冬日裏',
+'冰山里' => '冰山裏',
+'恺撒' => '凱撒',
+'函数里' => '函數裏',
'分布' => '分佈',
-'分布於' => '分佈於',
-'分布于' => '分佈於',
+'分占' => '分佔',
+'錢尼' => '切尼',
+'切尔诺贝利' => '切爾諾貝爾',
'列支敦斯登' => '列支敦士登',
+'別著' => '別着',
'賴比瑞亞' => '利比里亞',
+'刮著' => '刮着',
+'到山里' => '到山裏',
'制著' => '制着',
'制著作' => '制著作',
'制著名' => '制著名',
@@ -16739,6 +15021,13 @@ $zh2HK = array(
'刻著者' => '刻著者',
'刻著述' => '刻著述',
'刻著錄' => '刻著錄',
+'前波莫瑞' => '前波美拉尼亞',
+'剪彩' => '剪綵',
+'割占' => '割佔',
+'劃著' => '劃着',
+'擊劍' => '劍擊',
+'击剑' => '劍擊',
+'加薩走廊' => '加沙地帶',
'迦納' => '加納',
'加彭' => '加蓬',
'努力著' => '努力着',
@@ -16757,6 +15046,7 @@ $zh2HK = array(
'努著者' => '努著者',
'努著述' => '努著述',
'努著錄' => '努著錄',
+'布蘭登堡' => '勃蘭登堡',
'動著' => '動着',
'動著作' => '動著作',
'動著名' => '動著名',
@@ -16765,11 +15055,22 @@ $zh2HK = array(
'動著者' => '動著者',
'動著述' => '動著述',
'動著錄' => '動著錄',
-'北环线' => '北環綫',
-'北環線' => '北環綫',
-'医院里' => '医院裏',
+'包著' => '包着',
+'北朝鲜' => '北韓',
+'南朝鲜' => '南韓',
'波札那' => '博茨瓦納',
-'珍妮弗·卡普里亚蒂' => '卡佩雅蒂',
+'占卜' => '占卜',
+'占國橋' => '占國橋',
+'占国桥' => '占國橋',
+'占有五不' => '占有五不',
+'占著作' => '占著作',
+'占著稱' => '占著稱',
+'占著述' => '占著述',
+'占著錄' => '占著錄',
+'卡普里亚蒂' => '卡佩雅蒂',
+'喀拉蚩' => '卡拉奇',
+'卡斯楚' => '卡斯特羅',
+'臥' => '卧',
'印著' => '印着',
'印著作' => '印著作',
'印著名' => '印著名',
@@ -16779,14 +15080,13 @@ $zh2HK = array(
'印著述' => '印著述',
'印著錄' => '印著錄',
'瓜地馬拉' => '危地馬拉',
-'泡麵' => '即食麵',
-'方便面' => '即食麵',
-'快速面' => '即食麵',
-'速食麵' => '即食麵',
'厄瓜多' => '厄瓜多爾',
'厄瓜多爾' => '厄瓜多爾',
'厄瓜多尔' => '厄瓜多爾',
+'厄利垂亚' => '厄立特里亞',
'厄利垂亞' => '厄立特里亞',
+'源代码' => '原始碼',
+'去山里' => '去山裏',
'去著' => '去着',
'去著作' => '去著作',
'去著名' => '去著名',
@@ -16795,6 +15095,7 @@ $zh2HK = array(
'去著者' => '去著者',
'去著述' => '去著述',
'去著錄' => '去著錄',
+'参数里' => '參數裏',
'受著' => '受着',
'受著作' => '受著作',
'受著名' => '受著名',
@@ -16803,6 +15104,9 @@ $zh2HK = array(
'受著者' => '受著者',
'受著述' => '受著述',
'受著錄' => '受著錄',
+'丛林里' => '叢林裏',
+'口里' => '口裏',
+'只占' => '只佔',
'叫著' => '叫着',
'叫著作' => '叫著作',
'叫著名' => '叫著名',
@@ -16811,12 +15115,20 @@ $zh2HK = array(
'叫著者' => '叫著者',
'叫著述' => '叫著述',
'叫著錄' => '叫著錄',
+'古柯鹼' => '可卡因',
'叱吒' => '叱咤',
-'叱咤' => '叱咤',
+'斯皮尔伯格' => '史匹堡',
+'史匹柏' => '史匹堡',
+'史蒂芬·史匹柏' => '史提芬·史匹堡',
+'斯蒂芬·斯皮尔伯格' => '史提芬·史匹堡',
'吃不著' => '吃不着',
'吃得著' => '吃得着',
'吃著' => '吃着',
+'吃里扒外' => '吃裏扒外',
+'吃里爬外' => '吃裏爬外',
'吉布地' => '吉布堤',
+'吉尔吉斯斯坦' => '吉爾吉斯',
+'吊著' => '吊着',
'向著' => '向着',
'向著作' => '向著作',
'向著名' => '向著名',
@@ -16825,6 +15137,8 @@ $zh2HK = array(
'向著者' => '向著者',
'向著述' => '向著述',
'向著錄' => '向著錄',
+'吞占' => '吞佔',
+'吧台' => '吧枱',
'含著' => '含着',
'含著作' => '含著作',
'含著名' => '含著名',
@@ -16841,6 +15155,8 @@ $zh2HK = array(
'吹著者' => '吹著者',
'吹著述' => '吹著述',
'吹著錄' => '吹著錄',
+'呆著' => '呆着',
+'呆里呆气' => '呆裏呆氣',
'味著' => '味着',
'味著作' => '味著作',
'味著名' => '味著名',
@@ -16849,8 +15165,12 @@ $zh2HK = array(
'味著者' => '味著者',
'味著述' => '味著述',
'味著錄' => '味著錄',
-'咤' => '咤',
+'咖哩' => '咖喱',
+'麦克风' => '咪高峰',
+'麥克風' => '咪高峰',
+'哥特式' => '哥德式',
'哥斯大黎加' => '哥斯達黎加',
+'哪里' => '哪裏',
'哭著' => '哭着',
'哭著作' => '哭著作',
'哭著名' => '哭著名',
@@ -16875,12 +15195,16 @@ $zh2HK = array(
'喝著者' => '喝著者',
'喝著述' => '喝著述',
'喝著錄' => '喝著錄',
-'自行车' => '單車',
+'乔治·奥威尔' => '喬治·歐威爾',
+'單眼相機' => '單鏡反光機',
+'单反相机' => '單鏡反光機',
'嗅不著' => '嗅不着',
'嗅得著' => '嗅得着',
'嗅著' => '嗅着',
+'凱薩琳' => '嘉芙蓮',
+'凯瑟琳' => '嘉芙蓮',
+'嘯吒' => '嘯咤',
'嘴里' => '嘴裏',
-'嘴裡' => '嘴裏',
'嚷著' => '嚷着',
'嚷著作' => '嚷著作',
'嚷著名' => '嚷著名',
@@ -16889,7 +15213,11 @@ $zh2HK = array(
'嚷著者' => '嚷著者',
'嚷著述' => '嚷著述',
'嚷著錄' => '嚷著錄',
+'回著' => '回着',
+'回著名' => '回著名',
'因著' => '因着',
+'因著〈' => '因著〈',
+'因著《' => '因著《',
'因著作' => '因著作',
'因著名' => '因著名',
'因著書' => '因著書',
@@ -16905,6 +15233,9 @@ $zh2HK = array(
'困著者' => '困著者',
'困著述' => '困著述',
'困著錄' => '困著錄',
+'圈占' => '圈佔',
+'圈里' => '圈裏',
+'西洋棋' => '國際象棋',
'圍著' => '圍着',
'圍著作' => '圍著作',
'圍著名' => '圍著名',
@@ -16913,9 +15244,13 @@ $zh2HK = array(
'圍著者' => '圍著者',
'圍著述' => '圍著述',
'圍著錄' => '圍著錄',
+'园里' => '園裏',
'吐瓦魯' => '圖瓦盧',
-'土豆網' => '土豆網',
-'土豆网' => '土豆網',
+'土魯斯' => '圖盧茲',
+'图里的' => '圖裏的',
+'图里,' => '圖裏,',
+'土里' => '土裏',
+'在山里' => '在山裏',
'在著' => '在着',
'在著作' => '在著作',
'在著名' => '在著名',
@@ -16925,6 +15260,11 @@ $zh2HK = array(
'在著述' => '在著述',
'在著錄' => '在著錄',
'蓋亞那' => '圭亞那',
+'地占' => '地佔',
+'地图里' => '地圖裏',
+'IP' => '地址',
+'堪培拉' => '坎培拉',
+'坐台' => '坐枱',
'坐著' => '坐着',
'坐著作' => '坐著作',
'坐著名' => '坐著名',
@@ -16933,11 +15273,19 @@ $zh2HK = array(
'坐著者' => '坐著者',
'坐著述' => '坐著述',
'坐著錄' => '坐著錄',
+'坑里' => '坑裏',
'坦尚尼亞' => '坦桑尼亞',
-'衣索匹亞' => '埃塞俄比亞',
+'衣索匹亞' => '埃塞俄比亚',
'衣索比亞' => '埃塞俄比亞',
+'葉里溫' => '埃里溫',
+'城里' => '城裏',
+'域里' => '域裏',
'吉里巴斯' => '基里巴斯',
+'场里' => '場裏',
+'塗著' => '塗着',
'塞普勒斯' => '塞浦路斯',
+'賽普勒斯' => '塞浦路斯',
+'塞爾維亞與蒙特內哥羅' => '塞爾維亞和黑山',
'塞席爾' => '塞舌爾',
'壓著' => '壓着',
'壓著作' => '壓著作',
@@ -16947,10 +15295,12 @@ $zh2HK = array(
'壓著者' => '壓著者',
'壓著述' => '壓著述',
'壓著錄' => '壓著錄',
-'夏天里' => '夏天裏',
-'夏天裡' => '夏天裏',
-'夏日里' => '夏日裏',
-'夏日裡' => '夏日裏',
+'壶里' => '壺裏',
+'多占' => '多佔',
+'夜晚里' => '夜晚裏',
+'夜里' => '夜裏',
+'夢有五不占' => '夢有五不占',
+'梦有五不占' => '夢有五不占',
'夢著' => '夢着',
'夢著作' => '夢著作',
'夢著名' => '夢著名',
@@ -16959,7 +15309,9 @@ $zh2HK = array(
'夢著者' => '夢著者',
'夢著述' => '夢著述',
'夢著錄' => '夢著錄',
-'大卫·贝克汉姆' => '大衛碧咸',
+'梦里' => '夢裏',
+'天里' => '天裏',
+'宇航员' => '太空人',
'夾著' => '夾着',
'夾著作' => '夾著作',
'夾著名' => '夾著名',
@@ -16968,6 +15320,19 @@ $zh2HK = array(
'夾著者' => '夾著者',
'夾著述' => '夾著述',
'夾著錄' => '夾著錄',
+'奧占' => '奧佔',
+'奥占' => '奧佔',
+'歐巴馬' => '奧巴馬',
+'妆台' => '妝枱',
+'威斯伐倫' => '威斯特法倫',
+'威爾士' => '威爾斯',
+'威尔士' => '威爾斯',
+'子里' => '子裏',
+'字里行间' => '字裏行間',
+'存著' => '存着',
+'存著名' => '存著名',
+'孟德爾頌' => '孟德爾遜',
+'门德尔松' => '孟德爾遜',
'孤著' => '孤着',
'孤著作' => '孤著作',
'孤著名' => '孤著名',
@@ -16984,7 +15349,6 @@ $zh2HK = array(
'學著者' => '學著者',
'學著述' => '學著述',
'學著錄' => '學著錄',
-'學裡' => '學裏',
'学里' => '學裏',
'守著' => '守着',
'守著作' => '守著作',
@@ -16994,6 +15358,7 @@ $zh2HK = array(
'守著者' => '守著者',
'守著述' => '守著述',
'守著錄' => '守著錄',
+'安哈特' => '安哈爾特',
'安地卡及巴布達' => '安提瓜和巴布達',
'定著' => '定着',
'定著作' => '定著作',
@@ -17003,9 +15368,14 @@ $zh2HK = array(
'定著者' => '定著者',
'定著述' => '定著述',
'定著錄' => '定著錄',
+'宣布' => '宣佈',
+'宫里' => '宮裏',
+'家里' => '家裏',
+'密布' => '密佈',
+'密西根' => '密歇根',
'沃尓沃' => '富豪',
-'寒假裡' => '寒假裏',
-'寒假里' => '寒假裏',
+'寡占' => '寡佔',
+'写字台' => '寫字枱',
'寫著' => '寫着',
'寫著作' => '寫著作',
'寫著名' => '寫著名',
@@ -17014,10 +15384,13 @@ $zh2HK = array(
'寫著者' => '寫著者',
'寫著述' => '寫著述',
'寫著錄' => '寫著錄',
-'将军澳线' => '將軍澳綫',
-'將軍澳線' => '將軍澳綫',
+'宝里宝气' => '寶裏寶氣',
+'封面里' => '封面裏',
+'將占' => '將佔',
+'将占' => '將佔',
+'將占卜' => '將占卜',
+'将占卜' => '將占卜',
'专辑里' => '專輯裏',
-'專輯裡' => '專輯裏',
'尋著' => '尋着',
'尋著作' => '尋著作',
'尋著名' => '尋著名',
@@ -17034,12 +15407,12 @@ $zh2HK = array(
'對著者' => '對著者',
'對著述' => '對著述',
'對著錄' => '對著錄',
+'少占' => '少佔',
+'就里' => '就裏',
+'尼克松' => '尼克遜',
'奈及利亞' => '尼日利亞',
-'尼日利亚' => '尼日利亞',
-'尼日利亞' => '尼日利亞',
-'尼日尔' => '尼日爾',
-'尼日爾' => '尼日爾',
-'尼日' => '尼日爾',
+'局里' => '局裏',
+'屋里' => '屋裏',
'展著' => '展着',
'展著作' => '展著作',
'展著名' => '展著名',
@@ -17048,19 +15421,30 @@ $zh2HK = array(
'展著者' => '展著者',
'展著述' => '展著述',
'展著錄' => '展著錄',
-'山洞裡' => '山洞裏',
-'山洞里' => '山洞裏',
+'屯里' => '屯裏',
+'山里有' => '山裏有',
+'山里的' => '山裏的',
'甘比亞' => '岡比亞',
-'公車' => '巴士',
+'岸裡' => '岸裡',
+'工作台' => '工作枱',
+'已占' => '已佔',
+'巴塞罗那' => '巴塞隆拿',
+'巴塞隆納' => '巴塞隆拿',
'巴貝多' => '巴巴多斯',
'巴布亞紐幾內亞' => '巴布亞新畿內亞',
+'巴士拉' => '巴斯拉',
+'巷里' => '巷裏',
+'市占' => '市佔',
+'市里' => '市裏',
'布吉納法索' => '布基納法索',
-'布希亞' => '布希亞',
-'布希亚' => '布希亞',
-'布希' => '布殊',
'布什' => '布殊',
+'布里斯托尔' => '布里斯托',
'蒲隆地' => '布隆迪',
+'席哈克' => '希拉克',
+'希拉蕊' => '希拉里',
'希特勒' => '希特拉',
+'帛琉' => '帕勞',
+'巴尔米拉环礁' => '帕邁拉環礁',
'帕劳' => '帛琉',
'帶著' => '帶着',
'帶著作' => '帶著作',
@@ -17070,6 +15454,7 @@ $zh2HK = array(
'帶著者' => '帶著者',
'帶著述' => '帶著述',
'帶著錄' => '帶著錄',
+'幅图里' => '幅圖裏',
'幫著' => '幫着',
'幫著作' => '幫著作',
'幫著名' => '幫著名',
@@ -17081,10 +15466,15 @@ $zh2HK = array(
'干着急' => '干着急',
'賓士' => '平治',
'年代里' => '年代裏',
-'年代裡' => '年代裏',
-'幹著' => '幹着',
+'年里' => '年裏',
'干着' => '幹着',
+'幹著' => '幹着',
+'幹著名' => '幹著名',
+'幹著稱' => '幹著稱',
'幾內亞比索' => '幾內亞比紹',
+'店里' => '店裏',
+'坎城' => '康城',
+'戛纳' => '康城',
'康著' => '康着',
'康著作' => '康著作',
'康著名' => '康著名',
@@ -17093,6 +15483,16 @@ $zh2HK = array(
'康著者' => '康著者',
'康著述' => '康著述',
'康著錄' => '康著錄',
+'庙里' => '廟裏',
+'强占' => '強佔',
+'強占' => '強佔',
+'约翰斯顿岛' => '強斯頓環礁',
+'弹子台' => '彈子枱',
+'蹦床' => '彈床',
+'弹珠台' => '彈珠枱',
+'形上學' => '形而上學',
+'谢丽·布莱尔' => '彭雪玲',
+'往里' => '往裏',
'待著' => '待着',
'待著作' => '待著作',
'待著名' => '待著名',
@@ -17109,6 +15509,10 @@ $zh2HK = array(
'得著者' => '得著者',
'得著述' => '得著述',
'得著錄' => '得著錄',
+'从图里' => '從圖裏',
+'从山里' => '從山裏',
+'从里到外' => '從裏到外',
+'从里向外' => '從裏向外',
'循著' => '循着',
'循著作' => '循著作',
'循著名' => '循著名',
@@ -17117,8 +15521,13 @@ $zh2HK = array(
'循著者' => '循著者',
'循著述' => '循著述',
'循著錄' => '循著錄',
+'徵占' => '徵佔',
+'征占' => '徵佔',
+'德占' => '德佔',
+'得克萨斯' => '德克薩斯',
+'德勒斯登' => '德累斯頓',
+'澈底' => '徹底',
'心著' => '心着',
-'心繫著' => '心繫着',
'心著作' => '心著作',
'心著名' => '心著名',
'心著書' => '心著書',
@@ -17126,8 +15535,8 @@ $zh2HK = array(
'心著者' => '心著者',
'心著述' => '心著述',
'心著錄' => '心著錄',
-'心裡' => '心裏',
'心里' => '心裏',
+'心里面' => '心裏面',
'忍著' => '忍着',
'忍著作' => '忍著作',
'忍著名' => '忍著名',
@@ -17136,7 +15545,6 @@ $zh2HK = array(
'忍著者' => '忍著者',
'忍著述' => '忍著述',
'忍著錄' => '忍著錄',
-'志著' => '志着',
'志著作' => '志著作',
'志著名' => '志著名',
'志著書' => '志著書',
@@ -17152,6 +15560,7 @@ $zh2HK = array(
'忙著者' => '忙著者',
'忙著述' => '忙著述',
'忙著錄' => '忙著錄',
+'忙里' => '忙裏',
'急著' => '急着',
'急著作' => '急著作',
'急著名' => '急著名',
@@ -17168,6 +15577,7 @@ $zh2HK = array(
'性著者' => '性著者',
'性著述' => '性著述',
'性著錄' => '性著錄',
+'怪里怪气' => '怪裏怪氣',
'悠著' => '悠着',
'悠著作' => '悠著作',
'悠著名' => '悠著名',
@@ -17176,6 +15586,7 @@ $zh2HK = array(
'悠著者' => '悠著者',
'悠著述' => '悠著述',
'悠著錄' => '悠著錄',
+'悶著' => '悶着',
'想象' => '想像',
'想著' => '想着',
'想著作' => '想著作',
@@ -17185,7 +15596,10 @@ $zh2HK = array(
'想著者' => '想著者',
'想著述' => '想著述',
'想著錄' => '想著錄',
+'義占' => '意佔',
+'意占' => '意佔',
'義大利' => '意大利',
+'艾滋' => '愛滋',
'愛著' => '愛着',
'愛著作' => '愛著作',
'愛著名' => '愛著名',
@@ -17194,6 +15608,7 @@ $zh2HK = array(
'愛著者' => '愛著者',
'愛著述' => '愛著述',
'愛著錄' => '愛著錄',
+'慌里慌张' => '慌裏慌張',
'慣著' => '慣着',
'慣著作' => '慣著作',
'慣著名' => '慣著名',
@@ -17202,6 +15617,8 @@ $zh2HK = array(
'慣著者' => '慣著者',
'慣著述' => '慣著述',
'慣著錄' => '慣著錄',
+'宪法里' => '憲法裏',
+'应用程序' => '應用程式',
'應著' => '應着',
'應著作' => '應著作',
'應著名' => '應著名',
@@ -17218,6 +15635,7 @@ $zh2HK = array(
'懷著者' => '懷著者',
'懷著述' => '懷著述',
'懷著錄' => '懷著錄',
+'怀里' => '懷裏',
'戀著' => '戀着',
'戀著作' => '戀著作',
'戀著名' => '戀著名',
@@ -17226,6 +15644,7 @@ $zh2HK = array(
'戀著者' => '戀著者',
'戀著述' => '戀著述',
'戀著錄' => '戀著錄',
+'戈巴契夫' => '戈爾巴喬夫',
'戰著' => '戰着',
'戰著作' => '戰著作',
'戰著名' => '戰著名',
@@ -17234,10 +15653,13 @@ $zh2HK = array(
'戰著者' => '戰著者',
'戰著述' => '戰著述',
'戰著錄' => '戰著錄',
-'戲裡' => '戲裏',
+'戏彩娱亲' => '戲綵娛親',
+'戲彩娛親' => '戲綵娛親',
'戏里' => '戲裏',
-'黛安娜' => '戴安娜',
+'撒切尔' => '戴卓爾',
+'柴契爾' => '戴卓爾',
'狄安娜' => '戴安娜',
+'黛安娜' => '戴安娜',
'戴著' => '戴着',
'戴著作' => '戴著作',
'戴著名' => '戴著名',
@@ -17246,7 +15668,11 @@ $zh2HK = array(
'戴著者' => '戴著者',
'戴著述' => '戴著述',
'戴著錄' => '戴著錄',
+'房里' => '房裏',
+'所占' => '所佔',
'索羅門群島' => '所羅門群島',
+'手里' => '手裏',
+'手里剑' => '手裏劍',
'列印' => '打印',
'印表機' => '打印機',
'打著' => '打着',
@@ -17360,6 +15786,8 @@ $zh2HK = array(
'捆著者' => '捆著者',
'捆著述' => '捆著述',
'捆著錄' => '捆著錄',
+'俯卧撑' => '掌上壓',
+'伏地挺身' => '掌上壓',
'掖著' => '掖着',
'掖著作' => '掖著作',
'掖著名' => '掖著名',
@@ -17375,7 +15803,7 @@ $zh2HK = array(
'掙著者' => '掙著者',
'掙著述' => '掙著述',
'掙著錄' => '掙著錄',
-'掛鉤' => '掛鈎',
+'掛著' => '掛着',
'接著' => '接着',
'接著作' => '接著作',
'接著名' => '接著名',
@@ -17405,6 +15833,9 @@ $zh2HK = array(
'揮著者' => '揮著者',
'揮著述' => '揮著述',
'揮著錄' => '揮著錄',
+'搜索引擎' => '搜尋引擎',
+'抢占' => '搶佔',
+'搶占' => '搶佔',
'摟著' => '摟着',
'摟著作' => '摟著作',
'摟著名' => '摟著名',
@@ -17412,6 +15843,8 @@ $zh2HK = array(
'摟著者' => '摟著者',
'摟著述' => '摟著述',
'摟著錄' => '摟著錄',
+'折台' => '摺枱',
+'撒马尔罕' => '撒馬爾罕',
'撼著' => '撼着',
'撼著作' => '撼著作',
'撼著名' => '撼著名',
@@ -17427,6 +15860,7 @@ $zh2HK = array(
'擋著者' => '擋著者',
'擋著述' => '擋著述',
'擋著錄' => '擋著錄',
+'擔著' => '擔着',
'據著' => '據着',
'據著作' => '據著作',
'據著名' => '據著名',
@@ -17435,6 +15869,9 @@ $zh2HK = array(
'據著者' => '據著者',
'據著述' => '據著述',
'據著錄' => '據著錄',
+'擡著' => '擡着',
+'摆布' => '擺佈',
+'擺布' => '擺佈',
'擺著' => '擺着',
'擺著作' => '擺著作',
'擺著名' => '擺著名',
@@ -17442,8 +15879,12 @@ $zh2HK = array(
'擺著者' => '擺著者',
'擺著述' => '擺著述',
'擺著錄' => '擺著錄',
-'故事里' => '故事裏',
-'故事裡' => '故事裏',
+'攻占' => '攻佔',
+'放著' => '放着',
+'放著作' => '放著作',
+'放著名' => '放著名',
+'放著称' => '放著稱',
+'放著稱' => '放著稱',
'敞著' => '敞着',
'敞著作' => '敞著作',
'敞著名' => '敞著名',
@@ -17451,7 +15892,15 @@ $zh2HK = array(
'敞著者' => '敞著者',
'敞著述' => '敞著述',
'敞著錄' => '敞著錄',
+'散布' => '散佈',
'數著' => '數着',
+'数字技术' => '數碼技術',
+'數位技術' => '數碼技術',
+'數位相機' => '數碼相機',
+'數碼訊號' => '數碼訊號',
+'数字信号' => '數碼訊號',
+'數位電視' => '數碼電視',
+'数字电视' => '數碼電視',
'數著作' => '數著作',
'數著名' => '數著名',
'數著稱' => '數著稱',
@@ -17468,10 +15917,14 @@ $zh2HK = array(
'斥著錄' => '斥著錄',
'史瓦濟蘭' => '斯威士蘭',
'斯洛維尼亞' => '斯洛文尼亞',
-'新著龍虎門' => '新著龍虎門',
+'紐澳良' => '新奧爾良',
'紐西蘭' => '新西蘭',
-'日子里' => '日子裏',
-'日子裡' => '日子裏',
+'方法里' => '方法裏',
+'族里' => '族裏',
+'日占' => '日佔',
+'日里' => '日裏',
+'昂山素季' => '昂山素姬',
+'翁山蘇姬' => '昂山素姬',
'昂著' => '昂着',
'昂著作' => '昂著作',
'昂著名' => '昂著名',
@@ -17480,6 +15933,8 @@ $zh2HK = array(
'昂著者' => '昂著者',
'昂著述' => '昂著述',
'昂著錄' => '昂著錄',
+'星羅棋布' => '星羅棋佈',
+'星罗棋布' => '星羅棋佈',
'映著' => '映着',
'映著作' => '映著作',
'映著名' => '映著名',
@@ -17488,14 +15943,6 @@ $zh2HK = array(
'映著者' => '映著者',
'映著述' => '映著述',
'映著錄' => '映著錄',
-'春假里' => '春假裏',
-'春假裡' => '春假裏',
-'春天裡' => '春天裏',
-'春天里' => '春天裏',
-'春日裡' => '春日裏',
-'春日里' => '春日裏',
-'时间里' => '時間裏',
-'時間裡' => '時間裏',
'晃著' => '晃着',
'晃著作' => '晃著作',
'晃著名' => '晃著名',
@@ -17503,8 +15950,13 @@ $zh2HK = array(
'晃著者' => '晃著者',
'晃著述' => '晃著述',
'晃著錄' => '晃著錄',
-'暑假里' => '暑假裏',
-'暑假裡' => '暑假裏',
+'芯片' => '晶片',
+'晶元' => '晶片',
+'智慧型' => '智能',
+'智慧卡' => '智能卡',
+'智慧手機' => '智能手機',
+'暗地里' => '暗地裏',
+'暗沟里' => '暗溝裏',
'暗著' => '暗着',
'暗著作' => '暗著作',
'暗著名' => '暗著名',
@@ -17513,6 +15965,12 @@ $zh2HK = array(
'暗著者' => '暗著者',
'暗著述' => '暗著述',
'暗著錄' => '暗著錄',
+'暗里' => '暗裏',
+'會占' => '會佔',
+'会占' => '會佔',
+'會占卜' => '會占卜',
+'会占卜' => '會占卜',
+'会里' => '會裏',
'有著' => '有着',
'有著作' => '有著作',
'有著名' => '有著名',
@@ -17521,6 +15979,9 @@ $zh2HK = array(
'有著者' => '有著者',
'有著述' => '有著述',
'有著錄' => '有著錄',
+'罗纳德·里根' => '朗奴·列根',
+'罗纳尔多' => '朗拿度',
+'罗纳尔迪尼奥' => '朗拿甸奴',
'望著' => '望着',
'望著作' => '望著作',
'望著名' => '望著名',
@@ -17536,6 +15997,8 @@ $zh2HK = array(
'朝著者' => '朝著者',
'朝著述' => '朝著述',
'朝著錄' => '朝著錄',
+'板球' => '木球',
+'班傑明' => '本傑明',
'本著' => '本着',
'本著作' => '本著作',
'本著名' => '本著名',
@@ -17544,12 +16007,13 @@ $zh2HK = array(
'本著者' => '本著者',
'本著述' => '本著述',
'本著錄' => '本著錄',
-'村子里' => '村子裏',
-'村子裡' => '村子裏',
-'东涌线' => '東涌綫',
-'東涌線' => '東涌綫',
-'東鐵線' => '東鐵綫',
-'东铁线' => '東鐵綫',
+'里瓦尔多' => '李華度',
+'村里' => '村裏',
+'杜塞道夫' => '杜塞爾多夫',
+'迪拜' => '杜拜',
+'亚细安' => '東盟',
+'東協' => '東盟',
+'板著臉' => '板着臉',
'枕著' => '枕着',
'枕著作' => '枕著作',
'枕著名' => '枕著名',
@@ -17557,9 +16021,22 @@ $zh2HK = array(
'枕著者' => '枕著者',
'枕著述' => '枕著述',
'枕著錄' => '枕著錄',
+'檯' => '枱',
+'台布' => '枱布',
+'台历' => '枱曆',
+'台灯' => '枱燈',
+'台面上' => '枱面上',
+'柏林墙' => '柏林圍牆',
+'奧黛莉·朵杜' => '柯德莉·塔圖',
+'奥黛丽·赫本' => '柯德莉·夏萍',
+'奧黛麗·赫本' => '柯德莉·夏萍',
+'哥廷根' => '格丁根',
'格瑞那達' => '格林納達',
+'格鲁吉亚' => '格魯吉亞',
'撞球' => '桌球',
'台球' => '桌球',
+'梅鐸' => '梅鐸',
+'默多克' => '梅鐸',
'梳著' => '梳着',
'梳著作' => '梳著作',
'梳著名' => '梳著名',
@@ -17567,12 +16044,12 @@ $zh2HK = array(
'梳著者' => '梳著者',
'梳著述' => '梳著述',
'梳著錄' => '梳著錄',
-'森林裡' => '森林裏',
+'棉里' => '棉裏',
+'桑巴舞' => '森巴舞',
'森林里' => '森林裏',
-'棺材裡' => '棺材裏',
'棺材里' => '棺材裏',
-'榴蓮' => '榴槤',
'榴莲' => '榴槤',
+'榴蓮' => '榴槤',
'樂著' => '樂着',
'樂著作' => '樂著作',
'樂著名' => '樂著名',
@@ -17581,14 +16058,17 @@ $zh2HK = array(
'樂著者' => '樂著者',
'樂著述' => '樂著述',
'樂著錄' => '樂著錄',
+'標志著' => '標志着',
'寶獅' => '標致',
'標誌著' => '標誌着',
-'機場快線' => '機場快綫',
-'机场快线' => '機場快綫',
+'树林里' => '樹林裏',
+'工具機' => '機床',
'機器人' => '機械人',
'机器人' => '機械人',
+'柜台' => '櫃枱',
'历史里' => '歷史裏',
-'歷史裡' => '歷史裏',
+'死里求生' => '死裏求生',
+'死里逃生' => '死裏逃生',
'殺著' => '殺着',
'殺著作' => '殺著作',
'殺著名' => '殺著名',
@@ -17597,9 +16077,14 @@ $zh2HK = array(
'殺著者' => '殺著者',
'殺著述' => '殺著述',
'殺著錄' => '殺著錄',
+'壳里' => '殼裏',
'茅利塔尼亞' => '毛里塔尼亞',
-'毛里求斯' => '毛里裘斯',
'模里西斯' => '毛里裘斯',
+'毛里求斯' => '毛里裘斯',
+'公厘' => '毫米',
+'公釐' => '毫米',
+'水来汤里去' => '水來湯裏去',
+'水里' => '水裏',
'求著' => '求着',
'求著作' => '求著作',
'求著名' => '求著名',
@@ -17608,7 +16093,10 @@ $zh2HK = array(
'求著者' => '求著者',
'求著述' => '求著述',
'求著錄' => '求著錄',
+'池里' => '池裏',
+'汙' => '污',
'文莱' => '汶萊',
+'沈著' => '沈着',
'沉著' => '沉着',
'沉著作' => '沉著作',
'沉著名' => '沉著名',
@@ -17617,13 +16105,14 @@ $zh2HK = array(
'沉著者' => '沉著者',
'沉著述' => '沉著述',
'沉著錄' => '沉著錄',
-'沙中线' => '沙中綫',
-'沙中線' => '沙中綫',
-'沙地阿拉伯' => '沙特阿拉伯',
+'沖著' => '沖着',
+'沖著。' => '沖著。',
+'沖著《' => '沖著《',
+'沖著,' => '沖著,',
'沙烏地阿拉伯' => '沙特阿拉伯',
-'沙田至中環線' => '沙田至中環綫',
-'沙田至中环线' => '沙田至中環綫',
-'马拉特·萨芬' => '沙芬',
+'沙地阿拉伯' => '沙特阿拉伯',
+'沙里淘金' => '沙裏淘金',
+'河里' => '河裏',
'沿著' => '沿着',
'沿著作' => '沿著作',
'沿著名' => '沿著名',
@@ -17632,7 +16121,13 @@ $zh2HK = array(
'沿著者' => '沿著者',
'沿著述' => '沿著述',
'沿著錄' => '沿著錄',
+'法占' => '法佔',
+'法里,' => '法裏,',
+'玻里尼西亞' => '波利尼西亞',
+'波士尼亞' => '波斯尼亞',
'波士尼亞赫塞哥維納' => '波斯尼亞黑塞哥維那',
+'幫浦' => '泵',
+'洞里' => '洞裏',
'辛巴威' => '津巴布韋',
'宏都拉斯' => '洪都拉斯',
'活著' => '活着',
@@ -17643,6 +16138,8 @@ $zh2HK = array(
'活著者' => '活著者',
'活著述' => '活著述',
'活著錄' => '活著錄',
+'移动网络' => '流動網絡',
+'行動網路' => '流動網絡',
'行動電話' => '流動電話',
'移动电话' => '流動電話',
'流著' => '流着',
@@ -17655,6 +16152,7 @@ $zh2HK = array(
'流著錄' => '流著錄',
'流露著' => '流露着',
'浮著' => '浮着',
+'蘭卡威' => '浮羅交怡',
'浮著作' => '浮著作',
'浮著名' => '浮著名',
'浮著書' => '浮著書',
@@ -17662,6 +16160,10 @@ $zh2HK = array(
'浮著者' => '浮著者',
'浮著述' => '浮著述',
'浮著錄' => '浮著錄',
+'海上布雷' => '海上佈雷',
+'海洛因' => '海洛英',
+'海灣布雷' => '海灣佈雷',
+'海湾布雷' => '海灣佈雷',
'涵著' => '涵着',
'涵著作' => '涵著作',
'涵著名' => '涵著名',
@@ -17678,10 +16180,8 @@ $zh2HK = array(
'涼著者' => '涼著者',
'涼著述' => '涼著述',
'涼著錄' => '涼著錄',
-'深淵裡' => '深淵裏',
-'深渊里' => '深渊裏',
-'港岛线' => '港島綫',
-'港島線' => '港島綫',
+'深山里' => '深山裏',
+'渊里' => '淵裏',
'渴著' => '渴着',
'渴著作' => '渴著作',
'渴著名' => '渴著名',
@@ -17690,6 +16190,11 @@ $zh2HK = array(
'渴著者' => '渴著者',
'渴著述' => '渴著述',
'渴著錄' => '渴著錄',
+'湊合著' => '湊合着',
+'湖里' => '湖裏',
+'准将' => '準將',
+'准將' => '準將',
+'准尉' => '準尉',
'溢著' => '溢着',
'溢著作' => '溢著作',
'溢著名' => '溢著名',
@@ -17706,6 +16211,8 @@ $zh2HK = array(
'演著者' => '演著者',
'演著述' => '演著述',
'演著錄' => '演著錄',
+'漠里' => '漠裏',
+'漢諾瓦' => '漢諾威',
'漫著' => '漫着',
'漫著作' => '漫著作',
'漫著名' => '漫著名',
@@ -17714,6 +16221,7 @@ $zh2HK = array(
'漫著者' => '漫著者',
'漫著述' => '漫著述',
'漫著錄' => '漫著錄',
+'潜意识里' => '潛意識裏',
'潤著' => '潤着',
'潤著作' => '潤著作',
'潤著名' => '潤著名',
@@ -17722,15 +16230,20 @@ $zh2HK = array(
'潤著者' => '潤著者',
'潤著述' => '潤著述',
'潤著錄' => '潤著錄',
-'無線劇集' => '無綫劇集',
-'无线剧集' => '無綫劇集',
-'無線收費' => '無綫收費',
-'无线收费' => '無綫收費',
-'无线节目' => '無綫節目',
-'無線節目' => '無綫節目',
-'无线电视' => '無綫電視',
-'無線電視' => '無綫電視',
+'潭里' => '潭裏',
+'溼' => '濕',
+'火山里' => '火山裏',
+'火箭布雷' => '火箭佈雷',
+'為著' => '為着',
+'為著《' => '為著《',
+'為著作' => '為著作',
+'為著名' => '為著名',
+'為著稱' => '為著稱',
+'為著者' => '為著者',
+'為著述' => '為著述',
+'為著錄' => '為著錄',
'菸' => '煙',
+'照占' => '照佔',
'照著' => '照着',
'照著作' => '照著作',
'照著名' => '照著名',
@@ -17755,6 +16268,9 @@ $zh2HK = array(
'爭著者' => '爭著者',
'爭著述' => '爭著述',
'爭著錄' => '爭著錄',
+'墙里' => '牆裏',
+'版图里' => '版圖裏',
+'版权信息' => '版權資訊',
'千里達托貝哥' => '特立尼達和多巴哥',
'牽著' => '牽着',
'牽著作' => '牽著作',
@@ -17773,7 +16289,7 @@ $zh2HK = array(
'犯不著述' => '犯不著述',
'犯不著錄' => '犯不著錄',
'犯得著' => '犯得着',
-'犬只' => '狗隻',
+'狂占' => '狂佔',
'猜著' => '猜着',
'猜著作' => '猜著作',
'猜著名' => '猜著名',
@@ -17783,7 +16299,8 @@ $zh2HK = array(
'猜著述' => '猜著述',
'猜著錄' => '猜著錄',
'狱里' => '獄裏',
-'獄裡' => '獄裏',
+'獨占' => '獨佔',
+'独占' => '獨佔',
'獨著' => '獨着',
'獨著作' => '獨著作',
'獨著名' => '獨著名',
@@ -17800,8 +16317,12 @@ $zh2HK = array(
'獲著者' => '獲著者',
'獲著述' => '獲著述',
'獲著錄' => '獲著錄',
-'諾魯' => '瑙魯',
+'班里' => '班裏',
+'球台' => '球枱',
+'卢塞恩' => '琉森',
+'諾鲁' => '瑙魯',
'萬那杜' => '瓦努阿圖',
+'肯尼迪' => '甘迺迪',
'甜著' => '甜着',
'甜著作' => '甜著作',
'甜著名' => '甜著名',
@@ -17812,6 +16333,7 @@ $zh2HK = array(
'甜著錄' => '甜著錄',
'用不著' => '用不着',
'用得著' => '用得着',
+'用法里' => '用法裏',
'用著' => '用着',
'用著作' => '用著作',
'用著名' => '用著名',
@@ -17820,6 +16342,11 @@ $zh2HK = array(
'用著者' => '用著者',
'用著述' => '用著述',
'用著錄' => '用著錄',
+'田里' => '田裏',
+'由表及里' => '由表及裏',
+'A型肝炎' => '甲型肝炎',
+'A肝' => '甲肝',
+'界里' => '界裏',
'留著' => '留着',
'留著作' => '留著作',
'留著名' => '留著名',
@@ -17828,14 +16355,29 @@ $zh2HK = array(
'留著者' => '留著者',
'留著述' => '留著述',
'留著錄' => '留著錄',
+'畫著' => '畫着',
+'畫著作' => '畫著作',
+'畫著名' => '畫著名',
+'畫著稱' => '畫著稱',
+'畫著者' => '畫著者',
'當著' => '當着',
'當著作' => '當著作',
+'過著作' => '當著作',
'當著名' => '當著名',
+'過著名' => '當著名',
+'過著書' => '當著書',
'當著書' => '當著書',
'當著稱' => '當著稱',
+'過著稱' => '當著稱',
'當著者' => '當著者',
+'過著者' => '當著者',
+'過著述' => '當著述',
'當著述' => '當著述',
+'過著錄' => '當著錄',
'當著錄' => '當著錄',
+'几内亚' => '畿內亞',
+'幾內亞' => '畿內亞',
+'迭代' => '疊代',
'疑著' => '疑着',
'疑著作' => '疑著作',
'疑著名' => '疑著名',
@@ -17844,14 +16386,22 @@ $zh2HK = array(
'疑著者' => '疑著者',
'疑著述' => '疑著述',
'疑著錄' => '疑著錄',
-'发布' => '發佈',
+'狂牛症' => '瘋牛症',
'發布' => '發佈',
-'百科裡' => '百科裏',
+'发布' => '發佈',
+'發著' => '發着',
+'發著《' => '發著《',
+'發著作' => '發著作',
+'發著名' => '發著名',
+'發著稱' => '發著稱',
+'發著者' => '發著者',
+'白里透红' => '白裏透紅',
+'戈登·布朗' => '白高敦',
'百科里' => '百科裏',
-'計程車' => '的士',
-'出租车' => '的士',
+'的图里' => '的圖裏',
+'的山里' => '的山裏',
+'皮里春秋' => '皮裏春秋',
'皮里阳秋' => '皮裏陽秋',
-'皮裡陽秋' => '皮裏陽秋',
'皺著' => '皺着',
'皺著作' => '皺著作',
'皺著名' => '皺著名',
@@ -17860,6 +16410,7 @@ $zh2HK = array(
'皺著者' => '皺著者',
'皺著述' => '皺著述',
'皺著錄' => '皺著錄',
+'盒里' => '盒裏',
'盛著' => '盛着',
'盛著作' => '盛著作',
'盛著名' => '盛著名',
@@ -17868,7 +16419,9 @@ $zh2HK = array(
'盛著者' => '盛著者',
'盛著述' => '盛著述',
'盛著錄' => '盛著錄',
+'盘里' => '盤裏',
'盧安達' => '盧旺達',
+'羅亞爾' => '盧瓦爾',
'盯著' => '盯着',
'盯著作' => '盯著作',
'盯著名' => '盯著名',
@@ -17887,6 +16440,7 @@ $zh2HK = array(
'盾著錄' => '盾著錄',
'看不著' => '看不着',
'看得著' => '看得着',
+'看法里' => '看法裏',
'看著' => '看着',
'看著作' => '看著作',
'看著名' => '看著名',
@@ -17895,9 +16449,10 @@ $zh2HK = array(
'看著者' => '看著者',
'看著述' => '看著述',
'看著錄' => '看著錄',
-'眼睛裡' => '眼睛裏',
+'眼眶里' => '眼眶裏',
'眼睛里' => '眼睛裏',
-'著什麼急' => '着什麼急',
+'眼里' => '眼裏',
+'著什' => '着什',
'著他' => '着他',
'著你' => '着你',
'著力' => '着力',
@@ -17917,6 +16472,7 @@ $zh2HK = array(
'著法' => '着法',
'著涼' => '着涼',
'著火' => '着火',
+'著甚麽' => '着甚麽',
'著眼' => '着眼',
'著祂' => '着祂',
'著筆' => '着筆',
@@ -17959,11 +16515,19 @@ $zh2HK = array(
'瞪著者' => '瞪著者',
'瞪著述' => '瞪著述',
'瞪著錄' => '瞪著錄',
-'簡訊' => '短訊',
+'智慧財產權' => '知識產權',
+'智財權' => '知識產權',
'短信' => '短訊',
-'硬件' => '硬件',
+'簡訊' => '短訊',
+'什勒斯維希' => '石勒蘇益格',
+'硅' => '矽',
+'硅藻' => '硅藻',
'硬體' => '硬件',
-'福斯' => '福士',
+'硬件' => '硬件',
+'贝克汉姆' => '碧咸',
+'贝克漢' => '碧咸',
+'社里' => '社裏',
+'福馬林' => '福爾馬林',
'福著' => '福着',
'福著作' => '福著作',
'福著名' => '福著名',
@@ -17972,14 +16536,20 @@ $zh2HK = array(
'福著者' => '福著者',
'福著述' => '福著述',
'福著錄' => '福著錄',
-'秋假裡' => '秋假裏',
-'秋假里' => '秋假裏',
-'秋天裡' => '秋天裏',
-'秋天里' => '秋天裏',
-'秋日里' => '秋日裏',
-'秋日裡' => '秋日裏',
+'秀发布' => '秀發佈',
+'私下里' => '私下裏',
+'隱私' => '私隱',
+'隐私' => '私隱',
'葛摩' => '科摩羅',
+'程序员' => '程式設計師',
'捷豹' => '積架',
+'穩占' => '穩佔',
+'稳占' => '穩佔',
+'穫著' => '穫着',
+'空中布雷' => '空中佈雷',
+'空投布雷' => '空投佈雷',
+'空氣品質' => '空氣質素',
+'空气质量' => '空氣質素',
'空著' => '空着',
'空著作' => '空著作',
'空著名' => '空著名',
@@ -17988,8 +16558,8 @@ $zh2HK = array(
'空著者' => '空著者',
'空著述' => '空著述',
'空著錄' => '空著錄',
-'太空梭' => '穿梭機',
'航天飞机' => '穿梭機',
+'太空梭' => '穿梭機',
'穿著' => '穿着',
'穿著作' => '穿著作',
'穿著名' => '穿著名',
@@ -17998,6 +16568,7 @@ $zh2HK = array(
'穿著者' => '穿著者',
'穿著述' => '穿著述',
'穿著錄' => '穿著錄',
+'窝里' => '窩裏',
'站著' => '站着',
'站著作' => '站著作',
'站著名' => '站著名',
@@ -18006,6 +16577,7 @@ $zh2HK = array(
'站著者' => '站著者',
'站著述' => '站著述',
'站著錄' => '站著錄',
+'竪著' => '竪着',
'笑著' => '笑着',
'笑著作' => '笑著作',
'笑著名' => '笑著名',
@@ -18014,6 +16586,8 @@ $zh2HK = array(
'笑著者' => '笑著者',
'笑著述' => '笑著述',
'笑著錄' => '笑著錄',
+'笑里藏刀' => '笑裏藏刀',
+'提比里西' => '第比利斯',
'管著' => '管着',
'管著作' => '管著作',
'管著名' => '管著名',
@@ -18022,10 +16596,24 @@ $zh2HK = array(
'管著者' => '管著者',
'管著述' => '管著述',
'管著錄' => '管著錄',
-'迈克尔·欧文' => '米高奧雲',
-'系列裡' => '系列裏',
+'箱里' => '箱裏',
+'节目里' => '節目裏',
+'簽著' => '簽着',
+'籃板球' => '籃板球',
+'麦克尔' => '米高',
+'迈克尔' => '米高',
+'迈克尔·欧文' => '米高·奧雲',
+'糊里糊涂' => '糊裏糊塗',
'系列里' => '系列裏',
+'係數' => '系數',
+'系里' => '系裏',
+'約占' => '約佔',
+'约占' => '約佔',
+'紐賓士域' => '紐賓士域',
+'索忍尼辛' => '索贊尼辛',
+'索尔仁尼琴' => '索贊尼辛',
'索馬利亞' => '索馬里',
+'索馬利里' => '索馬里',
'紮著' => '紮着',
'紮著作' => '紮著作',
'紮著名' => '紮著名',
@@ -18034,6 +16622,10 @@ $zh2HK = array(
'紮著者' => '紮著者',
'紮著述' => '紮著述',
'紮著錄' => '紮著錄',
+'组里' => '組裏',
+'吉他' => '結他',
+'结彩' => '結綵',
+'結彩' => '結綵',
'綁著' => '綁着',
'綁著作' => '綁著作',
'綁著名' => '綁著名',
@@ -18043,7 +16635,30 @@ $zh2HK = array(
'綁著述' => '綁著述',
'綁著錄' => '綁著錄',
'網路' => '網絡',
+'网里' => '網裏',
+'彩带' => '綵帶',
+'彩帶' => '綵帶',
+'彩排' => '綵排',
+'彩楼' => '綵樓',
+'彩樓' => '綵樓',
+'彩牌楼' => '綵牌樓',
+'彩牌樓' => '綵牌樓',
+'彩球' => '綵球',
+'彩綢' => '綵綢',
+'彩绸' => '綵綢',
+'彩线' => '綵綫',
+'彩線' => '綵線',
+'彩船' => '綵船',
+'彩衣' => '綵衣',
+'线图里' => '線圖裏',
'緝凶' => '緝兇',
+'县里' => '縣裏',
+'缝里' => '縫裏',
+'总数里' => '總數裏',
+'尖峰時段' => '繁忙時段',
+'尖峰時間' => '繁忙時間',
+'正體中文' => '繁體中文',
+'繃著' => '繃着',
'繞著' => '繞着',
'繞著作' => '繞著作',
'繞著名' => '繞著名',
@@ -18052,6 +16667,8 @@ $zh2HK = array(
'繞著者' => '繞著者',
'繞著述' => '繞著述',
'繞著錄' => '繞著錄',
+'繫著' => '繫着',
+'系着' => '繫着',
'纏著' => '纏着',
'纏著作' => '纏著作',
'纏著名' => '纏著名',
@@ -18076,6 +16693,8 @@ $zh2HK = array(
'罵著者' => '罵著者',
'罵著述' => '罵著述',
'罵著錄' => '罵著錄',
+'卢浮宫' => '羅浮宮',
+'美占' => '美佔',
'美著' => '美着',
'美著作' => '美著作',
'美著名' => '美著名',
@@ -18093,6 +16712,8 @@ $zh2HK = array(
'耀著述' => '耀著述',
'耀著錄' => '耀著錄',
'寮國' => '老撾',
+'寮人民民主共和國' => '老撾人民民主共和國',
+'寮語' => '老撾語',
'考著' => '考着',
'考著作' => '考著作',
'考著名' => '考著名',
@@ -18117,9 +16738,9 @@ $zh2HK = array(
'聽著述' => '聽著述',
'聽著錄' => '聽著錄',
'肚里' => '肚裏',
-'肚裡' => '肚裏',
'肯尼亚' => '肯雅',
-'肯亞' => '肯雅',
+'胃里' => '胃裏',
+'背地里' => '背地裏',
'背著' => '背着',
'背著作' => '背著作',
'背著名' => '背著名',
@@ -18128,6 +16749,8 @@ $zh2HK = array(
'背著者' => '背著者',
'背著述' => '背著述',
'背著錄' => '背著錄',
+'胡里胡涂' => '胡裏胡塗',
+'腰里' => '腰裏',
'膠著' => '膠着',
'膠著作' => '膠著作',
'膠著名' => '膠著名',
@@ -18144,6 +16767,7 @@ $zh2HK = array(
'臨著者' => '臨著者',
'臨著述' => '臨著述',
'臨著錄' => '臨著錄',
+'自行火炮' => '自走炮',
'與著' => '與着',
'與著作' => '與著作',
'與著名' => '與著名',
@@ -18152,7 +16776,14 @@ $zh2HK = array(
'與著者' => '與著者',
'與著述' => '與著述',
'與著錄' => '與著錄',
-'迈克尔·舒马赫' => '舒麥加',
+'舒马赫' => '舒麥加',
+'爱荷华' => '艾奧瓦',
+'愛荷華' => '艾奧瓦',
+'埃菲尔' => '艾菲爾',
+'帕塔亚' => '芭達亞',
+'花盆里' => '花盆裏',
+'苑里' => '苑裏',
+'苑裡' => '苑裡',
'苦著' => '苦着',
'苦著作' => '苦著作',
'苦著名' => '苦著名',
@@ -18162,13 +16793,27 @@ $zh2HK = array(
'苦著述' => '苦著述',
'苦著錄' => '苦著錄',
'苦里' => '苦裏',
-'苦裡' => '苦裏',
-'荃湾线' => '荃灣綫',
-'荃灣線' => '荃灣綫',
+'英占' => '英佔',
+'大英國協' => '英聯邦',
+'共和联邦' => '英聯邦',
+'草丛里' => '草叢裏',
+'霍爾斯坦' => '荷爾斯泰因',
+'好萊塢' => '荷里活',
+'好莱坞' => '荷里活',
+'庄里' => '莊裏',
'莫三比克' => '莫桑比克',
+'瓦倫西亞' => '華倫西亞',
+'巴伦西亚' => '華倫西亞',
+'巴倫西亞' => '華倫西亞',
+'瓦文萨' => '華里沙',
+'華勒沙' => '華里沙',
+'菲利普亲王' => '菲臘親王',
+'菲利普親王' => '菲臘親王',
'賴索托' => '萊索托',
-'馬自達' => '萬事得',
+'马恩岛' => '萌島',
'马自达' => '萬事得',
+'馬自達' => '萬事得',
+'万历朝鲜战争' => '萬曆朝鮮戰爭',
'落著' => '落着',
'落著作' => '落著作',
'落著名' => '落著名',
@@ -18177,6 +16822,11 @@ $zh2HK = array(
'落著者' => '落著者',
'落著述' => '落著述',
'落著錄' => '落著錄',
+'葉爾欽' => '葉利欽',
+'葡占' => '葡佔',
+'葫芦里卖甚么药' => '葫蘆裏賣甚麼藥',
+'蒙特婁' => '蒙特利爾',
+'滿地可' => '蒙特利爾',
'蒙著' => '蒙着',
'蒙著作' => '蒙著作',
'蒙著名' => '蒙著名',
@@ -18185,6 +16835,12 @@ $zh2HK = array(
'蒙著者' => '蒙著者',
'蒙著述' => '蒙著述',
'蒙著錄' => '蒙著錄',
+'蓋著' => '蓋着',
+'肖斯塔科维奇' => '蕭士達高維契',
+'蕭士塔高維奇' => '蕭士達高維契',
+'肖邦' => '蕭邦',
+'薛丁格' => '薛定諤',
+'塞拉耶佛' => '薩拉熱窩',
'萨达姆' => '薩達姆',
'藉著' => '藉着',
'藏著' => '藏着',
@@ -18211,6 +16867,10 @@ $zh2HK = array(
'蘸著者' => '蘸著者',
'蘸著述' => '蘸著述',
'蘸著錄' => '蘸著錄',
+'蜜里调油' => '蜜裏調油',
+'荧屏' => '螢屏',
+'屏幕' => '螢幕',
+'首席执行官' => '行政總裁',
'行著' => '行着',
'行著作' => '行著作',
'行著名' => '行著名',
@@ -18219,7 +16879,7 @@ $zh2HK = array(
'行著者' => '行著者',
'行著述' => '行著述',
'行著錄' => '行著錄',
-'衛' => '衞',
+'衝著' => '衝着',
'衣著' => '衣着',
'衣著作' => '衣著作',
'衣著名' => '衣著名',
@@ -18228,10 +16888,32 @@ $zh2HK = array(
'衣著者' => '衣著者',
'衣著述' => '衣著述',
'衣著錄' => '衣著錄',
-'裡勾外連' => '裏勾外連',
+'表里' => '表裏',
+'表里一致' => '表裏一致',
+'表里不一' => '表裏不一',
+'表里如一' => '表裏如一',
+'表里山河' => '表裏山河',
+'袋里' => '袋裏',
+'袖里' => '袖裏',
+'被里' => '被裏',
+'裡' => '裏',
'里勾外连' => '裏勾外連',
+'里屋' => '裏屋',
+'里层' => '裏層',
+'里带' => '裏帶',
+'里弦' => '裏弦',
+'里应外合' => '裏應外合',
+'里手' => '裏手',
+'里海' => '裏海',
+'里脊' => '裏脊',
+'里衣' => '裏衣',
+'里通外国' => '裏通外國',
+'里通外敌' => '裏通外敵',
+'里边' => '裏邊',
+'里间' => '裏間',
'里面' => '裏面',
-'裡面' => '裏面',
+'里面包' => '裏面包',
+'里头' => '裏頭',
'裝著' => '裝着',
'裝著作' => '裝著作',
'裝著名' => '裝著名',
@@ -18240,6 +16922,7 @@ $zh2HK = array(
'裝著者' => '裝著者',
'裝著述' => '裝著述',
'裝著錄' => '裝著錄',
+'裡冷' => '裡冷',
'裹著' => '裹着',
'裹著作' => '裹著作',
'裹著名' => '裹著名',
@@ -18248,8 +16931,13 @@ $zh2HK = array(
'裹著者' => '裹著者',
'裹著述' => '裹著述',
'裹著錄' => '裹著錄',
-'西铁线' => '西鐵綫',
-'西鐵線' => '西鐵綫',
+'衬里' => '襯裏',
+'西占' => '西佔',
+'塞维利亚' => '西維爾',
+'塞維亞' => '西維爾',
+'要占' => '要佔',
+'要占卜' => '要占卜',
+'覆著' => '覆着',
'見著' => '見着',
'見著作' => '見著作',
'見著名' => '見著名',
@@ -18258,8 +16946,10 @@ $zh2HK = array(
'見著者' => '見著者',
'見著述' => '見著述',
'見著錄' => '見著錄',
-'觀塘線' => '觀塘綫',
-'观塘线' => '觀塘綫',
+'角落里' => '角落裏',
+'分辨率' => '解像度',
+'解析度' => '解像度',
+'計畫' => '計劃',
'記著' => '記着',
'記著作' => '記著作',
'記著名' => '記著名',
@@ -18276,6 +16966,8 @@ $zh2HK = array(
'試著者' => '試著者',
'試著述' => '試著述',
'試著錄' => '試著錄',
+'话里有话' => '話裏有話',
+'语法里' => '語法裏',
'語著' => '語着',
'語著作' => '語著作',
'語著名' => '語著名',
@@ -18284,7 +16976,14 @@ $zh2HK = array(
'語著者' => '語著者',
'語著述' => '語著述',
'語著錄' => '語著錄',
+'语里' => '語裏',
+'說著' => '說着',
+'說著作' => '說著作',
+'說著稱' => '說著稱',
+'說著者' => '說著者',
+'說著述' => '說著述',
'數據機' => '調制解調器',
+'诺曼底' => '諾曼第',
'警戒著' => '警戒着',
'變著' => '變着',
'變著作' => '變著作',
@@ -18302,6 +17001,8 @@ $zh2HK = array(
'豎著者' => '豎著者',
'豎著述' => '豎著述',
'豎著錄' => '豎著錄',
+'象徵著名' => '象徵著名',
+'象徵著' => '象著着',
'豫著' => '豫着',
'豫著作' => '豫著作',
'豫著名' => '豫著名',
@@ -18310,7 +17011,8 @@ $zh2HK = array(
'豫著者' => '豫著者',
'豫著述' => '豫著述',
'豫著錄' => '豫著錄',
-'貝南' => '貝寧',
+'貝爾格勒' => '貝爾格萊德',
+'布莱尔' => '貝理雅',
'貞著' => '貞着',
'貞著作' => '貞著作',
'貞著名' => '貞著名',
@@ -18319,8 +17021,21 @@ $zh2HK = array(
'貞著者' => '貞著者',
'貞著述' => '貞著述',
'貞著錄' => '貞著錄',
+'負著' => '負着',
'買凶' => '買兇',
+'費占' => '費佔',
+'费占' => '費佔',
+'赌台' => '賭枱',
'尚比亞' => '贊比亞',
+'西臺人' => '赫梯人',
+'西臺國' => '赫梯國',
+'西臺帝' => '赫梯帝',
+'西臺文' => '赫梯文',
+'西臺族' => '赫梯族',
+'西臺王' => '赫梯王',
+'西臺語' => '赫梯語',
+'赫魯雪夫' => '赫魯曉夫',
+'走為上著' => '走為上着',
'走著' => '走着',
'走著作' => '走著作',
'走著名' => '走著名',
@@ -18369,6 +17084,7 @@ $zh2HK = array(
'跪著者' => '跪著者',
'跪著述' => '跪著述',
'跪著錄' => '跪著錄',
+'路图里' => '路圖裏',
'跳著' => '跳着',
'跳著作' => '跳著作',
'跳著名' => '跳著名',
@@ -18416,7 +17132,13 @@ $zh2HK = array(
'躺著者' => '躺著者',
'躺著述' => '躺著述',
'躺著錄' => '躺著錄',
+'车库里' => '車庫裏',
+'车站里' => '車站裏',
+'车里' => '車裏',
+'车里雅宾斯克' => '車里雅賓斯克',
'軟體' => '軟件',
+'軟體動物' => '軟體動物',
+'軟體家具' => '軟體家具',
'載著' => '載着',
'載著作' => '載著作',
'載著名' => '載著名',
@@ -18433,6 +17155,7 @@ $zh2HK = array(
'轉著者' => '轉著者',
'轉著述' => '轉著述',
'轉著錄' => '轉著錄',
+'办公台' => '辦公枱',
'辦著' => '辦着',
'辦著作' => '辦著作',
'辦著名' => '辦著名',
@@ -18441,10 +17164,6 @@ $zh2HK = array(
'辦著者' => '辦著者',
'辦著述' => '辦著述',
'辦著錄' => '辦著錄',
-'近角聪信' => '近角聰信',
-'近角聰信' => '近角聰信',
-'迪士尼线' => '迪士尼綫',
-'迪士尼線' => '迪士尼綫',
'迫著' => '迫着',
'追著' => '追着',
'追著作' => '追著作',
@@ -18462,9 +17181,21 @@ $zh2HK = array(
'逆著者' => '逆著者',
'逆著述' => '逆著述',
'逆著錄' => '逆著錄',
-'這里' => '這裏',
-'這裡' => '這裏',
+'径入' => '逕入',
+'径到' => '逕到',
+'径取' => '逕取',
+'径启' => '逕啟',
+'径寄' => '逕寄',
+'径庭' => '逕庭',
+'径往' => '逕往',
+'径自' => '逕自',
+'径行' => '逕行',
+'径迎' => '逕迎',
+'这里' => '這裏',
+'连占' => '連佔',
+'連占' => '連佔',
'連著' => '連着',
+'链接' => '連結',
'連著作' => '連著作',
'連著名' => '連著名',
'連著書' => '連著書',
@@ -18472,6 +17203,9 @@ $zh2HK = array(
'連著者' => '連著者',
'連著述' => '連著述',
'連著錄' => '連著錄',
+'进占' => '進佔',
+'進占' => '進佔',
+'演化論' => '進化論',
'逼著' => '逼着',
'逼著作' => '逼著作',
'逼著名' => '逼著名',
@@ -18488,6 +17222,10 @@ $zh2HK = array(
'遇著者' => '遇著者',
'遇著述' => '遇著述',
'遇著錄' => '遇著錄',
+'遍布' => '遍佈',
+'過著' => '過着',
+'达·芬奇' => '達·文西',
+'达芬奇' => '達文西',
'達著' => '達着',
'達著作' => '達著作',
'達著名' => '達著名',
@@ -18504,6 +17242,14 @@ $zh2HK = array(
'遠著者' => '遠著者',
'遠著述' => '遠著述',
'遠著錄' => '遠著錄',
+'還占' => '還佔',
+'还占' => '還佔',
+'邋里邋遢' => '邋裏邋遢',
+'那里' => '那裏',
+'奥斯曼' => '鄂圖曼',
+'配合著' => '配合着',
+'配合著名' => '配合著名',
+'配图里' => '配圖裏',
'配著' => '配着',
'配著作' => '配著作',
'配著名' => '配著名',
@@ -18521,13 +17267,12 @@ $zh2HK = array(
'醜著者' => '醜著者',
'醜著述' => '醜著述',
'醜著錄' => '醜著錄',
-'醫院裡' => '醫院裏',
'醯壺' => '醯壺',
'醯壶' => '醯壺',
'醯醋' => '醯醋',
'醯醢' => '醯醢',
-'醯醬' => '醯醬',
'醯酱' => '醯醬',
+'醯醬' => '醯醬',
'醯鸡' => '醯雞',
'醯雞' => '醯雞',
'釀著' => '釀着',
@@ -18538,8 +17283,8 @@ $zh2HK = array(
'釀著者' => '釀著者',
'釀著述' => '釀著述',
'釀著錄' => '釀著錄',
+'金装玉里' => '金裝玉裏',
'鉤' => '鈎',
-'鉤心鬥角' => '鈎心鬥角',
'鋪著' => '鋪着',
'鋪著作' => '鋪著作',
'鋪著名' => '鋪著名',
@@ -18548,6 +17293,11 @@ $zh2HK = array(
'鋪著者' => '鋪著者',
'鋪著述' => '鋪著述',
'鋪著錄' => '鋪著錄',
+'镜图里' => '鏡圖裏',
+'钟在寺里' => '鐘在寺裏',
+'狄托' => '鐵托',
+'泰坦尼克号' => '鐵達尼號',
+'门里' => '門裏',
'閉著' => '閉着',
'閉著作' => '閉著作',
'閉著名' => '閉著名',
@@ -18556,6 +17306,7 @@ $zh2HK = array(
'閉著者' => '閉著者',
'閉著述' => '閉著述',
'閉著錄' => '閉著錄',
+'蓋曼群島' => '開曼群島',
'開著' => '開着',
'開著作' => '開著作',
'開著名' => '開著名',
@@ -18564,6 +17315,8 @@ $zh2HK = array(
'開著者' => '開著者',
'開著述' => '開著述',
'開著錄' => '開著錄',
+'开诚布公' => '開誠佈公',
+'開誠布公' => '開誠佈公',
'閑著' => '閑着',
'閑著作' => '閑著作',
'閑著名' => '閑著名',
@@ -18572,6 +17325,9 @@ $zh2HK = array(
'閑著者' => '閑著者',
'閑著述' => '閑著述',
'閑著錄' => '閑著錄',
+'閒著' => '閒着',
+'间里' => '間裏',
+'關係著' => '關係着',
'關著' => '關着',
'關著作' => '關著作',
'關著名' => '關著名',
@@ -18584,7 +17340,11 @@ $zh2HK = array(
'聞得著' => '闻得着',
'聞著' => '闻着',
'亞塞拜然' => '阿塞拜疆',
+'阿布達比' => '阿布扎比',
'阿拉伯聯合大公國' => '阿拉伯聯合酋長國',
+'亞斯文' => '阿斯旺',
+'阿联酋' => '阿聯酋',
+'艾里爾·夏隆' => '阿里埃勒·沙龍',
'附著' => '附着',
'附著作' => '附著作',
'附著名' => '附著名',
@@ -18601,6 +17361,7 @@ $zh2HK = array(
'陋著者' => '陋著者',
'陋著述' => '陋著述',
'陋著錄' => '陋著錄',
+'院里' => '院裏',
'陪著' => '陪着',
'陪著作' => '陪著作',
'陪著名' => '陪著名',
@@ -18609,6 +17370,7 @@ $zh2HK = array(
'陪著者' => '陪著者',
'陪著述' => '陪著述',
'陪著錄' => '陪著錄',
+'阴沟里翻船' => '陰溝裏翻船',
'隔著' => '隔着',
'隔著作' => '隔著作',
'隔著名' => '隔著名',
@@ -18625,7 +17387,11 @@ $zh2HK = array(
'隨著者' => '隨著者',
'隨著述' => '隨著述',
'隨著錄' => '隨著錄',
+'隱占' => '隱佔',
+'隐占' => '隱佔',
+'雅爾達' => '雅爾塔',
'雅著' => '雅着',
+'雅穆索戈' => '雅穆蘇克雷',
'雅著作' => '雅著作',
'雅著名' => '雅著名',
'雅著書' => '雅著書',
@@ -18633,6 +17399,8 @@ $zh2HK = array(
'雅著者' => '雅著者',
'雅著述' => '雅著述',
'雅著錄' => '雅著錄',
+'集数里' => '集數裏',
+'集里' => '集裏',
'雜著' => '雜着',
'雜著作' => '雜著作',
'雜著名' => '雜著名',
@@ -18641,11 +17409,21 @@ $zh2HK = array(
'雜著者' => '雜著者',
'雜著述' => '雜著述',
'雜著錄' => '雜著錄',
+'鸡蛋里挑骨头' => '雞蛋裏挑骨頭',
'冰淇淋' => '雪糕',
-'雪里红' => '雪裏紅',
-'雪裡紅' => '雪裏紅',
-'雪裡蕻' => '雪裏蕻',
-'雪里蕻' => '雪裏蕻',
+'冰激凌' => '雪糕',
+'雪里' => '雪裏',
+'萊特灣' => '雷伊泰灣',
+'莱特湾' => '雷伊泰灣',
+'晶體管' => '電晶體',
+'晶体管' => '電晶體',
+'电脑程序' => '電腦程式',
+'计算机程序' => '電腦程式',
+'霄裡' => '霄裡',
+'荷姆茲' => '霍爾木茲',
+'雾里' => '霧裏',
+'霸占' => '霸佔',
+'非占不可' => '非佔不可',
'靠著' => '靠着',
'靠著作' => '靠著作',
'靠著名' => '靠著名',
@@ -18655,6 +17433,9 @@ $zh2HK = array(
'靠著述' => '靠著述',
'靠著錄' => '靠著錄',
'靠著录' => '靠著錄',
+'鞋里' => '鞋裏',
+'鞭辟入里' => '鞭辟入裏',
+'朝鲜战争' => '韓戰',
'響著' => '響着',
'響著作' => '響著作',
'響著名' => '響著名',
@@ -18679,10 +17460,8 @@ $zh2HK = array(
'順著者' => '順著者',
'順著述' => '順著述',
'順著錄' => '順著錄',
-'頒布' => '頒佈',
'颁布' => '頒佈',
-'領域裡' => '領域裏',
-'领域里' => '領域裏',
+'頒布' => '頒佈',
'領著' => '領着',
'領著作' => '領著作',
'領著名' => '領著名',
@@ -18691,6 +17470,10 @@ $zh2HK = array(
'領著者' => '領著者',
'領著述' => '領著述',
'領著錄' => '領著錄',
+'头里' => '頭裏',
+'风里' => '風裏',
+'颳著' => '颳着',
+'飃著' => '飃着',
'飄著' => '飄着',
'飄著作' => '飄著作',
'飄著名' => '飄著名',
@@ -18699,13 +17482,15 @@ $zh2HK = array(
'飄著者' => '飄著者',
'飄著述' => '飄著述',
'飄著錄' => '飄著錄',
-'館裡' => '館裏',
+'餐台' => '餐枱',
'馆里' => '館裏',
+'糊口' => '餬口',
+'马里兰' => '馬利蘭',
+'馬里蘭' => '馬利蘭',
+'马拉特·萨芬' => '馬拉特·沙芬',
+'馬斯垂克' => '馬斯特里赫特',
'馬爾地夫' => '馬爾代夫',
'馬利共和國' => '馬里共和國',
-'土豆' => '馬鈴薯',
-'馬鞍山線' => '馬鞍山綫',
-'马鞍山线' => '馬鞍山綫',
'駕著' => '駕着',
'駕著作' => '駕著作',
'駕著名' => '駕著名',
@@ -18730,6 +17515,8 @@ $zh2HK = array(
'騙著者' => '騙著者',
'騙著述' => '騙著述',
'騙著錄' => '騙著錄',
+'驶著' => '驶着',
+'高畫質' => '高清',
'高著' => '高着',
'高著作' => '高著作',
'高著名' => '高著名',
@@ -18746,6 +17533,7 @@ $zh2HK = array(
'髭著者' => '髭著者',
'髭著述' => '髭著述',
'髭著錄' => '髭著錄',
+'斗着' => '鬥着',
'鬥著' => '鬥着',
'鬥著作' => '鬥著作',
'鬥著名' => '鬥著名',
@@ -18754,6 +17542,11 @@ $zh2HK = array(
'鬥著者' => '鬥著者',
'鬥著述' => '鬥著述',
'鬥著錄' => '鬥著錄',
+'鬧著' => '鬧着',
+'牛軋' => '鳥結',
+'牛轧' => '鳥結',
+'鸠占' => '鳩佔',
+'鳩占' => '鳩佔',
'麗著' => '麗着',
'麗著作' => '麗著作',
'麗著名' => '麗著名',
@@ -18762,6 +17555,32 @@ $zh2HK = array(
'麗著者' => '麗著者',
'麗著述' => '麗著述',
'麗著錄' => '麗著錄',
+'麼著' => '麼着',
+'芮氏0' => '黎克特制0',
+'里氏0' => '黎克特制0',
+'芮氏1' => '黎克特制1',
+'里氏1' => '黎克特制1',
+'芮氏2' => '黎克特制2',
+'里氏2' => '黎克特制2',
+'芮氏3' => '黎克特制3',
+'里氏3' => '黎克特制3',
+'芮氏4' => '黎克特制4',
+'里氏4' => '黎克特制4',
+'里氏5' => '黎克特制5',
+'芮氏5' => '黎克特制5',
+'里氏6' => '黎克特制6',
+'芮氏6' => '黎克特制6',
+'里氏7' => '黎克特制7',
+'芮氏7' => '黎克特制7',
+'里氏8' => '黎克特制8',
+'芮氏8' => '黎克特制8',
+'芮氏9' => '黎克特制9',
+'里氏9' => '黎克特制9',
+'芮氏地震規模' => '黎克特制地震震級',
+'里氏地震规模' => '黎克特制地震震級',
+'里氏震级' => '黎克特制震級',
+'芮氏規模' => '黎克特制震級',
+'里氏规模' => '黎克特制震級',
'黏著' => '黏着',
'黏著作' => '黏著作',
'黏著名' => '黏著名',
@@ -18770,6 +17589,7 @@ $zh2HK = array(
'黏著者' => '黏著者',
'黏著述' => '黏著述',
'黏著錄' => '黏著錄',
+'蒙特內哥羅' => '黑山',
'點著' => '點着',
'點著作' => '點著作',
'點著名' => '點著名',
@@ -18778,204 +17598,1547 @@ $zh2HK = array(
'點著者' => '點著者',
'點著述' => '點著述',
'點著錄' => '點著錄',
-'點裡' => '點裏',
'点里' => '點裏',
+'点里程' => '點里程',
+'鼓里' => '鼓裏',
);
$zh2CN = array(
-'16進位' => '16进位',
'16進位制' => '16进位制',
-'『' => '‘',
-'』' => '’',
-'「' => '“',
-'」' => '”',
-'萬曆' => '万历',
+'16進位' => '16进制',
+'IP位址' => 'IP地址',
+'一份子' => '一分子',
+'全球資訊網' => '万维网',
+'三十六著' => '三十六着',
'三極體' => '三极管',
-'三極管' => '三极管',
-'串列加速器' => '串列加速器',
-'串列' => '串行',
+'下著' => '下着',
+'下著作' => '下著作',
+'下著名' => '下著名',
+'下著錄' => '下著录',
+'下著录' => '下著录',
+'下著有' => '下著有',
+'下著稱' => '下著称',
+'下著称' => '下著称',
+'下著者' => '下著者',
+'下著述' => '下著述',
+'不著' => '不着',
+'不著書' => '不著书',
+'不著名' => '不著名',
+'不著錄' => '不著录',
+'不著稱' => '不著称',
+'不著述' => '不著述',
+'與著' => '与着',
+'與著書' => '与著书',
+'與著作' => '与著作',
+'與著名' => '与著名',
+'與著錄' => '与著录',
+'與著稱' => '与著称',
+'與著者' => '与著者',
+'與著述' => '与著述',
+'醜著' => '丑着',
+'醜著書' => '丑著书',
+'醜著作' => '丑著作',
+'醜著名' => '丑著名',
+'醜著錄' => '丑著录',
+'醜著稱' => '丑著称',
+'醜著者' => '丑著者',
+'醜著述' => '丑著述',
+'邱吉爾' => '丘吉尔',
+'C型肝炎' => '丙型肝炎',
+'C肝' => '丙肝',
+'東協' => '东盟',
+'亚细安' => '东盟',
+'臨著' => '临着',
+'臨著書' => '临著书',
+'臨著作' => '临著作',
+'臨著名' => '临著名',
+'臨著錄' => '临著录',
+'臨著稱' => '临著称',
+'臨著者' => '临著者',
+'臨著述' => '临著述',
+'為著' => '为着',
+'為著《' => '为著《',
+'為著作' => '为著作',
+'為著名' => '为著名',
+'為著錄' => '为著录',
+'為著稱' => '为著称',
+'為著者' => '为著者',
+'為著述' => '为著述',
+'主機板' => '主板',
+'麗著' => '丽着',
+'麗著書' => '丽著书',
+'麗著作' => '丽著作',
+'麗著名' => '丽著名',
+'麗著錄' => '丽著录',
+'麗著稱' => '丽著称',
+'麗著者' => '丽著者',
+'麗著述' => '丽著述',
+'麼著' => '么着',
'烏茲別克' => '乌兹别克斯坦',
+'樂著' => '乐着',
+'樂著書' => '乐著书',
+'樂著作' => '乐著作',
+'樂著名' => '乐著名',
+'樂著錄' => '乐著录',
+'樂著稱' => '乐著称',
+'樂著者' => '乐著者',
+'樂著述' => '乐著述',
+'喬治·歐威爾' => '乔治·奥威尔',
+'乘著' => '乘着',
+'乘著書' => '乘著书',
+'乘著作' => '乘著作',
+'乘著名' => '乘著名',
+'乘著錄' => '乘著录',
+'乘著稱' => '乘著称',
+'乘著者' => '乘著者',
+'乘著述' => '乘著述',
+'B型肝炎' => '乙型肝炎',
+'B肝' => '乙肝',
+'吉力馬札羅' => '乞力马扎罗',
'葉門' => '也门',
-'芝士' => '乾酪',
-'二極管' => '二极管',
+'買帳' => '买账',
+'了結他' => '了结他',
+'爭著' => '争着',
+'爭著書' => '争著书',
+'爭著作' => '争著作',
+'爭著名' => '争著名',
+'爭著錄' => '争著录',
+'爭著稱' => '争著称',
+'爭著者' => '争著者',
+'爭著述' => '争著述',
'二極體' => '二极管',
'二進位制' => '二进位制',
'二進位' => '二进制',
'網際網路' => '互联网',
-'互聯網' => '互联网',
+'網際網絡' => '互联网',
+'亞歷山卓' => '亚历山大',
+'雅穆索戈' => '亚穆苏克罗',
'互動式' => '交互式',
+'交帳' => '交账',
+'亮著' => '亮着',
+'亮著書' => '亮著书',
+'亮著作' => '亮著作',
+'亮著名' => '亮著名',
+'亮著錄' => '亮著录',
+'亮著稱' => '亮著称',
+'亮著者' => '亮著者',
+'亮著述' => '亮著述',
'人工智慧' => '人工智能',
-'甚麽' => '什么',
'甚麼' => '什么',
+'甚麽' => '什么',
+'仗著' => '仗着',
+'仗著書' => '仗著书',
+'仗著作' => '仗著作',
+'仗著名' => '仗著名',
+'仗著錄' => '仗著录',
+'仗著稱' => '仗著称',
+'仗著者' => '仗著者',
+'仗著述' => '仗著述',
+'付帳' => '付账',
+'代表著' => '代表着',
+'代表著書' => '代表著书',
+'代表著作' => '代表著作',
+'代表著名' => '代表著名',
+'代表著錄' => '代表著录',
+'代表著稱' => '代表著称',
+'代表著者' => '代表著者',
+'代表著述' => '代表著述',
'乙太網' => '以太网',
+'伊莉莎白' => '伊丽莎白',
+'伊利諾' => '伊利诺伊',
+'伊利諾伊' => '伊利诺伊',
+'伊斯蘭瑪巴德' => '伊斯兰堡',
+'伊斯坦堡' => '伊斯坦布尔',
'優先順序' => '优先级',
-'感測' => '传感',
-'伯利茲' => '伯利兹',
+'傳著' => '传着',
+'傳著書' => '传著书',
+'傳著作' => '传著作',
+'傳著名' => '传著名',
+'傳著錄' => '传著录',
+'傳著稱' => '传著称',
+'傳著者' => '传著者',
+'傳著述' => '传著述',
'貝里斯' => '伯利兹',
+'伯明罕' => '伯明翰',
+'伴著' => '伴着',
+'伴著書' => '伴著书',
+'伴著作' => '伴著作',
+'伴著名' => '伴著名',
+'伴著錄' => '伴著录',
+'伴著稱' => '伴著称',
+'伴著者' => '伴著者',
+'伴著述' => '伴著述',
'點陣圖' => '位图',
+'IP' => '位址',
+'低著' => '低着',
+'低著書' => '低著书',
+'低著作' => '低著作',
+'低著名' => '低著名',
+'低著錄' => '低著录',
+'低著稱' => '低著称',
+'低著者' => '低著者',
+'低著述' => '低著述',
+'住著' => '住着',
+'住著書' => '住著书',
+'住著作' => '住著作',
+'住著名' => '住著名',
+'住著錄' => '住著录',
+'住著稱' => '住著称',
+'住著者' => '住著者',
+'住著述' => '住著述',
+'餘' => '余',
'維德角' => '佛得角',
-'常式' => '例程',
'侏儸紀' => '侏罗纪',
-'海珊' => '侯赛因',
+'側著' => '侧着',
+'側著書' => '侧著书',
+'側著作' => '侧著作',
+'側著名' => '侧著名',
+'側著錄' => '侧著录',
+'側著稱' => '侧著称',
+'側著者' => '侧著者',
+'側著述' => '侧著述',
+'可攜式' => '便携式',
'攜帶型' => '便携式',
+'保護著' => '保护着',
+'保障著' => '保障着',
+'保障著書' => '保障著书',
+'保障著作' => '保障著作',
+'保障著名' => '保障著名',
+'保障著錄' => '保障著录',
+'保障著稱' => '保障著称',
+'保障著者' => '保障著者',
+'保障著述' => '保障著述',
'資訊理論' => '信息论',
+'信著' => '信着',
+'信著書' => '信著书',
+'信著作' => '信著作',
+'信著名' => '信著名',
+'信著錄' => '信著录',
+'信著稱' => '信著称',
+'信著者' => '信著者',
+'信著述' => '信著述',
+'掌上壓' => '俯卧撑',
+'伏地挺身' => '俯卧撑',
+'倒帳' => '倒账',
+'候著' => '候着',
+'候著書' => '候著书',
+'候著作' => '候著作',
+'候著名' => '候著名',
+'候著錄' => '候著录',
+'候著稱' => '候著称',
+'候著者' => '候著者',
+'候著述' => '候著述',
+'藉著' => '借着',
+'借著' => '借着',
+'借著書' => '借著书',
+'借著作' => '借著作',
+'借著名' => '借著名',
+'借著錄' => '借著录',
+'借著稱' => '借著称',
+'借著者' => '借著者',
+'借著述' => '借著述',
+'假帳' => '假账',
+'做著' => '做着',
+'做著書' => '做著书',
+'做著作' => '做著作',
+'做著名' => '做著名',
+'做著錄' => '做著录',
+'做著稱' => '做著称',
+'做著者' => '做著者',
+'做著述' => '做著述',
+'偷著' => '偷着',
+'偷著書' => '偷著书',
+'偷著作' => '偷著作',
+'偷著名' => '偷著名',
+'偷著錄' => '偷著录',
+'偷著稱' => '偷著称',
+'偷著者' => '偷著者',
+'偷著述' => '偷著述',
+'傅利葉' => '傅里叶',
'母音' => '元音',
-'光碟' => '光盘',
+'光著' => '光着',
+'光著書' => '光著书',
+'光著作' => '光著作',
+'光著名' => '光著名',
+'光著錄' => '光著录',
+'光著稱' => '光著称',
+'光著者' => '光著者',
+'光著述' => '光著述',
'光碟機' => '光驱',
'柯林頓' => '克林顿',
'克羅埃西亞' => '克罗地亚',
-'進球' => '入球',
-'全形' => '全角',
+'轉殖' => '克隆',
+'複製人' => '克隆人',
+'入帳' => '入账',
'八進位制' => '八进位制',
'八進位' => '八进制',
+'西元1' => '公元1',
+'西元2' => '公元2',
+'西元3' => '公元3',
+'西元4' => '公元4',
+'西元5' => '公元5',
+'西元6' => '公元6',
+'西元7' => '公元7',
+'西元8' => '公元8',
+'西元9' => '公元9',
+'西元前' => '公元前',
+'公帳' => '公账',
'六進位制' => '六进位制',
'六進位' => '六进制',
+'關著' => '关着',
+'關係著' => '关系着',
+'關著書' => '关著书',
+'關著作' => '关著作',
+'關著名' => '关著名',
+'關著錄' => '关著录',
+'關著稱' => '关著称',
+'關著者' => '关著者',
+'關著述' => '关著述',
+'關帳' => '关账',
+'氧份' => '养分',
+'冀著' => '冀着',
+'冀著書' => '冀著书',
+'冀著作' => '冀著作',
+'冀著名' => '冀著名',
+'冀著錄' => '冀著录',
+'冀著稱' => '冀著称',
+'冀著者' => '冀著者',
+'冀著述' => '冀著述',
'記憶體' => '内存',
'甘比亞' => '冈比亚',
-'防寫' => '写保护',
-'軍中樂園' => '军中乐园',
-'冷菜' => '凉菜',
-'冷盤' => '凉菜',
+'冒著' => '冒着',
+'冒著書' => '冒著书',
+'冒著作' => '冒著作',
+'冒著名' => '冒著名',
+'冒著錄' => '冒著录',
+'冒著稱' => '冒著称',
+'冒著者' => '冒著者',
+'冒著述' => '冒著述',
+'寫著' => '写着',
+'寫著書' => '写著书',
+'寫著作' => '写著作',
+'寫著名' => '写著名',
+'寫著錄' => '写著录',
+'寫著稱' => '写著称',
+'寫著者' => '写著者',
+'寫著述' => '写著述',
+'沖著' => '冲着',
+'衝著' => '冲着',
+'沖著。' => '冲著。',
+'沖著《' => '冲著《',
+'沖著,' => '冲著,',
+'沖帳' => '冲账',
+'涼著' => '凉着',
+'涼著書' => '凉著书',
+'涼著作' => '凉著作',
+'涼著名' => '凉著名',
+'涼著錄' => '凉著录',
+'涼著稱' => '凉著称',
+'涼著者' => '凉著者',
+'涼著述' => '凉著述',
+'湊合著' => '凑合着',
+'畿內亞' => '几内亚',
'幾內亞比索' => '几内亚比绍',
-'梵谷' => '凡高',
-'計程車' => '出租车',
+'凱薩琳' => '凯瑟琳',
+'嘉芙蓮' => '凯瑟琳',
+'份內' => '分内',
+'份外' => '分外',
'解析度' => '分辨率',
+'解像度' => '分辨率',
+'份量' => '分量',
+'車諾比' => '切尔诺贝利',
+'劃著' => '划着',
+'李奧納多' => '列奥那多',
'列支敦斯登' => '列支敦士登',
'賴比瑞亞' => '利比里亚',
+'別著' => '别着',
+'刮著' => '刮着',
+'颳著' => '刮着',
+'到帳' => '到账',
+'制著' => '制着',
+'制著書' => '制著书',
+'制著作' => '制著作',
+'制著名' => '制著名',
+'制著錄' => '制著录',
+'制著稱' => '制著称',
+'制著者' => '制著者',
+'制著述' => '制著述',
+'煞車' => '刹车',
+'刻著' => '刻着',
+'刻著書' => '刻著书',
+'刻著作' => '刻著作',
+'刻著名' => '刻著名',
+'刻著錄' => '刻著录',
+'刻著稱' => '刻著称',
+'刻著者' => '刻著者',
+'刻著述' => '刻著述',
+'前波莫瑞' => '前波美拉尼亚',
+'辦著' => '办着',
+'辦著書' => '办著书',
+'辦著作' => '办著作',
+'辦著名' => '办著名',
+'辦著錄' => '办著录',
+'辦著稱' => '办著称',
+'辦著者' => '办著者',
+'辦著述' => '办著述',
+'加薩走廊' => '加沙地带',
'迦納' => '加纳',
'加彭' => '加蓬',
-'載入' => '加载',
+'動著' => '动着',
+'動著書' => '动著书',
+'動著作' => '动著作',
+'動著名' => '动著名',
+'動著錄' => '动著录',
+'動著稱' => '动著称',
+'動著者' => '动著者',
+'動著述' => '动著述',
+'努力著' => '努力着',
+'努力著書' => '努力著书',
+'努力著作' => '努力著作',
+'努力著名' => '努力著名',
+'努力著錄' => '努力著录',
+'努力著稱' => '努力著称',
+'努力著者' => '努力著者',
+'努力著述' => '努力著述',
+'努著' => '努着',
+'努著書' => '努著书',
+'努著作' => '努著作',
+'努著名' => '努著名',
+'努著錄' => '努著录',
+'努著稱' => '努著称',
+'努著者' => '努著者',
+'努著述' => '努著述',
+'蘿拉' => '劳拉',
+'布蘭登堡' => '勃兰登堡',
+'白朗寧' => '勃朗宁',
+'包著' => '包着',
+'北韓' => '北朝鲜',
'十進位制' => '十进位制',
'十進位' => '十进制',
-'半形' => '半角',
-'华乐街' => '华乐街',
+'公升' => '升',
+'單鏡反光機' => '单反相机',
+'單眼相機' => '单反相机',
'波札那' => '博茨瓦纳',
+'占著' => '占着',
+'占著作' => '占著作',
+'占著名' => '占著名',
+'占著者' => '占著者',
+'喀拉蚩' => '卡拉奇',
+'卡斯楚' => '卡斯特罗',
+'卡佩雅蒂' => '卡普里亚蒂',
'盧安達' => '卢旺达',
-'衞生' => '卫生',
-'衛生' => '卫生',
+'羅浮宮' => '卢浮宫',
+'羅亞爾' => '卢瓦尔',
+'印著' => '印着',
+'印著書' => '印著书',
+'印著作' => '印著作',
+'印著名' => '印著名',
+'印著錄' => '印著录',
+'印著稱' => '印著称',
+'印著者' => '印著者',
+'印著述' => '印著述',
'瓜地馬拉' => '危地马拉',
-'厄瓜多' => '厄瓜多尔',
-'厄瓜多爾' => '厄瓜多尔',
'厄瓜多尔' => '厄瓜多尔',
+'厄瓜多爾' => '厄瓜多尔',
+'厄瓜多' => '厄瓜多尔',
+'厄立特里亞' => '厄立特里亚',
+'厄利垂亚' => '厄立特里亚',
'厄利垂亞' => '厄立特里亚',
-'變數' => '变量',
+'壓著' => '压着',
+'壓著書' => '压著书',
+'壓著作' => '压著作',
+'壓著名' => '压著名',
+'壓著錄' => '压著录',
+'壓著稱' => '压著称',
+'壓著者' => '压著者',
+'壓著述' => '压著述',
+'去著' => '去着',
+'去著書' => '去著书',
+'去著作' => '去著作',
+'去著名' => '去著名',
+'去著錄' => '去著录',
+'去著稱' => '去著称',
+'去著者' => '去著者',
+'去著述' => '去著述',
+'發著' => '发着',
+'發著《' => '发著《',
+'發著作' => '发著作',
+'發著名' => '发著名',
+'發著稱' => '发著称',
+'發著者' => '发著者',
+'已開發國家' => '发达国家',
+'受著' => '受着',
+'受著書' => '受著书',
+'受著作' => '受著作',
+'受著名' => '受著名',
+'受著錄' => '受著录',
+'受著稱' => '受著称',
+'受著者' => '受著者',
+'受著述' => '受著述',
+'變著' => '变着',
+'變著書' => '变著书',
+'變著作' => '变著作',
+'變著名' => '变著名',
+'變著錄' => '变著录',
+'變著稱' => '变著称',
+'變著者' => '变著者',
+'變著述' => '变著述',
+'唯讀' => '只读',
+'叫著' => '叫着',
+'叫著書' => '叫著书',
+'叫著作' => '叫著作',
+'叫著名' => '叫著名',
+'叫著錄' => '叫著录',
+'叫著稱' => '叫著称',
+'叫著者' => '叫著者',
+'叫著述' => '叫著述',
'撞球' => '台球',
-'桌球' => '台球',
+'台帳' => '台账',
+'叱吒' => '叱咤',
+'吃著' => '吃着',
+'結他' => '吉他',
+'健力士世界紀錄' => '吉尼斯世界纪录',
+'金氏世界紀錄' => '吉尼斯世界纪录',
'吉布地' => '吉布提',
+'吊著' => '吊着',
+'名份' => '名分',
+'向著' => '向着',
+'向著書' => '向著书',
+'向著作' => '向著作',
+'向著名' => '向著名',
+'向著錄' => '向著录',
+'向著稱' => '向著称',
+'向著者' => '向著者',
+'向著述' => '向著述',
+'含著' => '含着',
+'含著書' => '含著书',
+'含著作' => '含著作',
+'含著名' => '含著名',
+'含著錄' => '含著录',
+'含著稱' => '含著称',
+'含著者' => '含著者',
+'含著述' => '含著述',
+'聽著' => '听着',
+'聽著書' => '听著书',
+'聽著作' => '听著作',
+'聽著名' => '听著名',
+'聽著錄' => '听著录',
+'聽著稱' => '听著称',
+'聽著者' => '听著者',
+'聽著述' => '听著述',
+'吹著' => '吹着',
+'吹著書' => '吹著书',
+'吹著作' => '吹著作',
+'吹著名' => '吹著名',
+'吹著錄' => '吹著录',
+'吹著稱' => '吹著称',
+'吹著者' => '吹著者',
+'吹著述' => '吹著述',
+'呆著' => '呆着',
+'呆帳' => '呆账',
+'味著' => '味着',
+'味著書' => '味著书',
+'味著作' => '味著作',
+'味著名' => '味著名',
+'味著錄' => '味著录',
+'味著稱' => '味著称',
+'味著者' => '味著者',
+'味著述' => '味著述',
+'咖哩' => '咖喱',
+'諮' => '咨',
'哈薩克' => '哈萨克斯坦',
+'響著' => '响着',
+'響著書' => '响著书',
+'響著作' => '响著作',
+'響著名' => '响著名',
+'響著錄' => '响著录',
+'響著稱' => '响著称',
+'響著者' => '响著者',
+'響著述' => '响著述',
'哥斯大黎加' => '哥斯达黎加',
+'哥德式' => '哥特式',
+'哭著' => '哭着',
+'哭著書' => '哭著书',
+'哭著作' => '哭著作',
+'哭著名' => '哭著名',
+'哭著錄' => '哭著录',
+'哭著稱' => '哭著称',
+'哭著者' => '哭著者',
+'哭著述' => '哭著述',
+'唱著' => '唱着',
+'唱著書' => '唱著书',
+'唱著作' => '唱著作',
+'唱著名' => '唱著名',
+'唱著錄' => '唱著录',
+'唱著稱' => '唱著称',
+'唱著者' => '唱著者',
+'唱著述' => '唱著述',
+'啸吒' => '啸咤',
+'喝著' => '喝着',
+'喝著書' => '喝著书',
+'喝著作' => '喝著作',
+'喝著名' => '喝著名',
+'喝著錄' => '喝著录',
+'喝著稱' => '喝著称',
+'喝著者' => '喝著者',
+'喝著述' => '喝著述',
+'嗅著' => '嗅着',
'雜訊' => '噪声',
-'因數' => '因子',
+'嚷著' => '嚷着',
+'嚷著書' => '嚷著书',
+'嚷著作' => '嚷著作',
+'嚷著名' => '嚷著名',
+'嚷著錄' => '嚷著录',
+'嚷著稱' => '嚷著称',
+'嚷著者' => '嚷著者',
+'嚷著述' => '嚷著述',
+'回著' => '回着',
+'回著名' => '回著名',
+'因著' => '因着',
+'因著〈' => '因著〈',
+'因著《' => '因著《',
+'因著書' => '因著书',
+'因著作' => '因著作',
+'因著名' => '因著名',
+'因著录' => '因著录',
+'因著錄' => '因著录',
+'因著稱' => '因著称',
+'因著者' => '因著者',
+'因著述' => '因著述',
+'西洋棋' => '囯际象棋',
+'困著' => '困着',
+'困著書' => '困著书',
+'困著作' => '困著作',
+'困著名' => '困著名',
+'困著錄' => '困著录',
+'困著稱' => '困著称',
+'困著者' => '困著者',
+'困著述' => '困著述',
+'圍著' => '围着',
+'圍著書' => '围著书',
+'圍著作' => '围著作',
+'圍著名' => '围著名',
+'圍著錄' => '围著录',
+'圍著稱' => '围著称',
+'圍著者' => '围著者',
+'圍著述' => '围著述',
+'韌體' => '固件',
+'土魯斯' => '图卢兹',
'吐瓦魯' => '图瓦卢',
+'原子筆' => '圆珠笔',
'土庫曼' => '土库曼斯坦',
'聖露西亞' => '圣卢西亚',
'聖吉斯納域斯' => '圣基茨和尼维斯',
'聖克里斯多福及尼維斯' => '圣基茨和尼维斯',
'聖文森及格瑞那丁' => '圣文森特和格林纳丁斯',
'聖馬利諾' => '圣马力诺',
+'在著' => '在着',
+'在著書' => '在著书',
+'在著作' => '在著作',
+'在著名' => '在著名',
+'在著錄' => '在著录',
+'在著稱' => '在著称',
+'在著者' => '在著者',
+'在著述' => '在著述',
'蓋亞那' => '圭亚那',
+'坐著' => '坐着',
+'坐著書' => '坐著书',
+'坐著作' => '坐著作',
+'坐著名' => '坐著名',
+'坐著錄' => '坐著录',
+'坐著稱' => '坐著称',
+'坐著者' => '坐著者',
+'坐著述' => '坐著述',
'坦尚尼亞' => '坦桑尼亚',
-'衣索比亞' => '埃塞俄比亚',
+'伊波拉' => '埃博拉',
'衣索匹亞' => '埃塞俄比亚',
+'衣索比亞' => '埃塞俄比亚',
+'艾菲爾' => '埃菲尔',
+'葉里溫' => '埃里温',
'功能變數名稱' => '域名',
'吉里巴斯' => '基里巴斯',
+'堂姊' => '堂姐',
+'坎培拉' => '堪培拉',
'塔吉克' => '塔吉克斯坦',
+'塔吉克斯坦' => '塔吉克斯坦',
+'塞爾維亞與蒙特內哥羅' => '塞尔维亚和黑山',
'塞拉利昂' => '塞拉利昂',
'塞普勒斯' => '塞浦路斯',
+'賽普勒斯' => '塞浦路斯',
+'西維爾' => '塞维利亚',
+'塞維亞' => '塞维利亚',
'塞席爾' => '塞舌尔',
'音效卡' => '声卡',
-'多米尼克' => '多米尼加国',
-'夜学' => '夜校',
-'福士' => '大众',
-'福斯' => '大众',
-'大衛碧咸' => '大卫·贝克汉姆',
-'頭槌' => '头球',
+'備著' => '备着',
+'備著書' => '备著书',
+'備著作' => '备著作',
+'備著名' => '备著名',
+'備著錄' => '备著录',
+'備著稱' => '备著称',
+'備著者' => '备著者',
+'備著述' => '备著述',
+'外部連結' => '外部链接',
+'托巴哥' => '多巴哥',
+'都卜勒' => '多普勒',
+'多明尼加' => '多米尼加',
+'大姊' => '大姐',
+'天份' => '天分',
+'夾著' => '夹着',
+'夾著書' => '夹著书',
+'夾著作' => '夹著作',
+'夾著名' => '夹著名',
+'夾著錄' => '夹著录',
+'夾著稱' => '夹著称',
+'夾著者' => '夹著者',
+'夾著述' => '夹著述',
'賓士' => '奔驰',
-'平治' => '奔驰',
+'歐巴馬' => '奥巴马',
+'柯德莉·夏萍' => '奥黛丽·赫本',
'忌廉' => '奶油',
-'字元会' => '字元会',
-'字元會' => '字元会',
-'字元濟' => '字元济',
-'字元济' => '字元济',
+'荷里活' => '好莱坞',
+'姊夫' => '姐夫',
+'姊姊' => '姐姐',
+'姊弟' => '姐弟',
+'威爾斯' => '威尔士',
+'威斯伐倫' => '威斯特法伦',
'字型大小' => '字号',
'字型檔' => '字库',
'欄位' => '字段',
-'字母' => '字母',
-'字元' => '字符',
-'字節' => '字节',
'位元組' => '字节',
-'存檔' => '存盘',
+'存著' => '存着',
+'存著名' => '存著名',
+'孤著' => '孤着',
+'孤著書' => '孤著书',
+'孤著作' => '孤著作',
+'孤著名' => '孤著名',
+'孤著錄' => '孤著录',
+'孤著稱' => '孤著称',
+'孤著者' => '孤著者',
+'孤著述' => '孤著述',
+'學姊' => '学姐',
+'學著' => '学着',
+'學著書' => '学著书',
+'學著作' => '学著作',
+'學著名' => '学著名',
+'學著錄' => '学著录',
+'學著稱' => '学著称',
+'學著者' => '学著者',
+'學著述' => '学著述',
+'太空飛行員' => '宇航员',
+'太空衣' => '宇航服',
+'守著' => '守着',
+'守著書' => '守著书',
+'守著作' => '守著作',
+'守著名' => '守著名',
+'守著錄' => '守著录',
+'守著稱' => '守著称',
+'守著者' => '守著者',
+'守著述' => '守著述',
+'安哈特' => '安哈尔特',
'安地卡及巴布達' => '安提瓜和巴布达',
'巨集' => '宏',
+'定著' => '定着',
+'定著書' => '定著书',
+'定著作' => '定著作',
+'定著名' => '定著名',
+'定著錄' => '定著录',
+'定著稱' => '定著称',
+'定著者' => '定著者',
+'定著述' => '定著述',
'寬頻' => '宽带',
-'定址' => '寻址',
+'密西根' => '密歇根',
+'密执安' => '密歇根',
+'對著' => '对着',
+'對著書' => '对著书',
+'對著作' => '对著作',
+'對著名' => '对著名',
+'對著錄' => '对著录',
+'對著稱' => '对著称',
+'對著者' => '对著者',
+'對著述' => '对著述',
+'對帳' => '对账',
+'尋著' => '寻着',
+'尋著書' => '寻著书',
+'尋著作' => '寻著作',
+'尋著名' => '寻著名',
+'尋著錄' => '寻著录',
+'尋著稱' => '寻著称',
+'尋著者' => '寻著者',
+'尋著述' => '寻著述',
+'飛彈' => '导弹',
+'祖雲達斯' => '尤文图斯',
'奈及利亞' => '尼日利亚',
-'尼日利亞' => '尼日利亚',
-'尼日利亚' => '尼日利亚',
'尼日爾' => '尼日尔',
-'尼日尔' => '尼日尔',
-'章節附註' => '尾注',
'區域網' => '局域网',
+'螢幕' => '屏幕',
+'展著' => '展着',
+'展著書' => '展著书',
+'展著作' => '展著作',
+'展著名' => '展著名',
+'展著錄' => '展著录',
+'展著稱' => '展著称',
+'展著者' => '展著者',
+'展著述' => '展著述',
+'華倫西亞' => '巴伦西亚',
+'瓦倫西亞' => '巴伦西亚',
+'巴塞隆納' => '巴塞罗那',
+'巴塞隆拿' => '巴塞罗那',
+'巴斯拉' => '巴士拉',
+'帕邁拉環礁' => '巴尔米拉环礁',
'巴貝多' => '巴巴多斯',
'巴布亞紐幾內亞' => '巴布亚新几内亚',
-'布希' => '布什',
'布殊' => '布什',
-'布基納法索' => '布基纳法索',
'布吉納法索' => '布基纳法索',
-'布希亞' => '布希亚',
-'布希亚' => '布希亚',
+'布隆泉' => '布隆方丹',
'蒲隆地' => '布隆迪',
+'席哈克' => '希拉克',
'希特拉' => '希特勒',
'帛琉' => '帕劳',
-'平治之乱' => '平治之乱',
-'平治之亂' => '平治之乱',
+'派屈克' => '帕特里克',
+'頻寬' => '带宽',
+'帶著' => '带着',
+'帶著書' => '带著书',
+'帶著作' => '带著作',
+'帶著名' => '带著名',
+'帶著錄' => '带著录',
+'帶著稱' => '带著称',
+'帶著者' => '带著者',
+'帶著述' => '带著述',
+'幫著' => '帮着',
+'幫著書' => '帮著书',
+'幫著作' => '帮著作',
+'幫著名' => '帮著名',
+'幫著錄' => '帮著录',
+'幫著稱' => '帮著称',
+'幫著者' => '帮著者',
+'幫著述' => '帮著述',
+'乾姊' => '干姐',
+'幹著' => '干着',
+'幹著名' => '幹著名',
+'幹著稱' => '幹著称',
+'庇護著' => '庇护着',
+'應用程式' => '应用程序',
+'應著' => '应着',
+'應著書' => '应著书',
+'應著作' => '应著作',
+'應著名' => '应著名',
+'應著錄' => '应著录',
+'應著稱' => '应著称',
+'應著者' => '应著者',
+'應著述' => '应著述',
+'康著' => '康着',
+'康著書' => '康著书',
+'康著作' => '康著作',
+'康著名' => '康著名',
+'康著錄' => '康著录',
+'康著稱' => '康著称',
+'康著者' => '康著者',
+'康著述' => '康著述',
+'建帳' => '建账',
+'克卜勒' => '开普勒',
+'蓋曼群島' => '开曼群岛',
+'開著' => '开着',
+'開著書' => '开著书',
+'開著作' => '开著作',
+'開著名' => '开著名',
+'開著錄' => '开著录',
+'開著稱' => '开著称',
+'開著者' => '开著者',
+'開著述' => '开著述',
+'開帳' => '开账',
'非同步' => '异步',
+'若且唯若' => '当且仅当',
+'當著' => '当着',
+'當著書' => '当著书',
+'當著作' => '当著作',
+'當著名' => '当著名',
+'當著錄' => '当著录',
+'當著稱' => '当著称',
+'當著者' => '当著者',
+'當著述' => '当著述',
+'錄影帶' => '录像带',
+'形上學' => '形而上学',
+'澈底' => '彻底',
+'逕入' => '径入',
+'逕到' => '径到',
+'逕取' => '径取',
+'逕啟' => '径启',
+'逕寄' => '径寄',
+'逕庭' => '径庭',
+'逕往' => '径往',
+'逕自' => '径自',
+'逕行' => '径行',
+'逕迎' => '径迎',
+'待著' => '待着',
+'待著書' => '待著书',
+'待著作' => '待著作',
+'待著名' => '待著名',
+'待著錄' => '待著录',
+'待著稱' => '待著称',
+'待著者' => '待著者',
+'待著述' => '待著述',
+'得著' => '得着',
+'得著書' => '得著书',
+'得著作' => '得著作',
+'得著名' => '得著名',
+'得著錄' => '得著录',
+'得著稱' => '得著称',
+'得著者' => '得著者',
+'得著述' => '得著述',
+'御姊' => '御姐',
'迴圈' => '循环',
-'快閃記憶體' => '快闪存储器',
+'循著' => '循着',
+'循著書' => '循著书',
+'循著作' => '循著作',
+'循著名' => '循著名',
+'循著錄' => '循著录',
+'循著稱' => '循著称',
+'循著者' => '循著者',
+'循著述' => '循著述',
+'德勒斯登' => '德累斯顿',
+'德希達' => '德里达',
+'心著' => '心着',
+'心著書' => '心著书',
+'心著作' => '心著作',
+'心著名' => '心著名',
+'心著錄' => '心著录',
+'心著稱' => '心著称',
+'心著者' => '心著者',
+'心著述' => '心著述',
+'忍著' => '忍着',
+'忍著書' => '忍著书',
+'忍著作' => '忍著作',
+'忍著名' => '忍著名',
+'忍著錄' => '忍著录',
+'忍著稱' => '忍著称',
+'忍著者' => '忍著者',
+'忍著述' => '忍著述',
+'志著' => '志着',
+'志著書' => '志著书',
+'志著作' => '志著作',
+'志著名' => '志著名',
+'志著錄' => '志著录',
+'志著稱' => '志著称',
+'志著者' => '志著者',
+'志著述' => '志著述',
+'忙著' => '忙着',
+'忙著書' => '忙著书',
+'忙著作' => '忙著作',
+'忙著名' => '忙著名',
+'忙著錄' => '忙著录',
+'忙著稱' => '忙著称',
+'忙著者' => '忙著者',
+'忙著述' => '忙著述',
+'懷著' => '怀着',
+'懷著書' => '怀著书',
+'懷著作' => '怀著作',
+'懷著名' => '怀著名',
+'懷著錄' => '怀著录',
+'懷著稱' => '怀著称',
+'懷著者' => '怀著者',
+'懷著述' => '怀著述',
+'急著' => '急着',
+'急著書' => '急著书',
+'急著作' => '急著作',
+'急著名' => '急著名',
+'急著錄' => '急著录',
+'急著稱' => '急著称',
+'急著者' => '急著者',
+'急著述' => '急著述',
+'性著' => '性着',
+'性著書' => '性著书',
+'性著作' => '性著作',
+'性著名' => '性著名',
+'性著錄' => '性著录',
+'性著稱' => '性著称',
+'性著者' => '性著者',
+'性著述' => '性著述',
'匯流排' => '总线',
+'總帳' => '总账',
+'戀著' => '恋着',
+'戀著書' => '恋著书',
+'戀著作' => '恋著作',
+'戀著名' => '恋著名',
+'戀著錄' => '恋著录',
+'戀著稱' => '恋著称',
+'戀著者' => '恋著者',
+'戀著述' => '恋著述',
+'恰如其份' => '恰如其分',
+'悠著' => '悠着',
+'悠著書' => '悠著书',
+'悠著作' => '悠著作',
+'悠著名' => '悠著名',
+'悠著錄' => '悠著录',
+'悠著稱' => '悠著称',
+'悠著者' => '悠著者',
+'悠著述' => '悠著述',
+'慣著' => '惯着',
+'慣著書' => '惯著书',
+'慣著作' => '惯著作',
+'慣著名' => '惯著名',
+'慣著錄' => '惯著录',
+'慣著稱' => '惯著称',
+'慣著者' => '惯著者',
+'慣著述' => '惯著述',
+'想著' => '想着',
+'想著書' => '想著书',
+'想著作' => '想著作',
+'想著名' => '想著名',
+'想著錄' => '想著录',
+'想著稱' => '想著称',
+'想著者' => '想著者',
+'想著述' => '想著述',
'義大利' => '意大利',
+'戈巴契夫' => '戈尔巴乔夫',
+'成份' => '成分',
+'戰著' => '战着',
+'戰著書' => '战著书',
+'戰著作' => '战著作',
+'戰著名' => '战著名',
+'戰著錄' => '战著录',
+'戰著稱' => '战著称',
+'戰著者' => '战著者',
+'戰著述' => '战著述',
+'坎城' => '戛纳',
'黛安娜' => '戴安娜',
-'屋价' => '房价',
+'戴著' => '戴着',
+'戴著書' => '戴著书',
+'戴著作' => '戴著作',
+'戴著名' => '戴著名',
+'戴著錄' => '戴著录',
+'戴著稱' => '戴著称',
+'戴著者' => '戴著者',
+'戴著述' => '戴著述',
'索羅門群島' => '所罗门群岛',
-'打印' => '打印',
+'紮著' => '扎着',
+'紮著書' => '扎著书',
+'紮著作' => '扎著作',
+'紮著名' => '扎著名',
+'紮著錄' => '扎著录',
+'紮著稱' => '扎著称',
+'紮著者' => '扎著者',
+'紮著述' => '扎著述',
'列印' => '打印',
'印表機' => '打印机',
-'打印機' => '打印机',
-'射門' => '打门',
-'掃瞄器' => '扫瞄仪',
-'括弧' => '括号',
-'拿破崙' => '拿破仑',
+'打著' => '打着',
+'打著書' => '打著书',
+'打著作' => '打著作',
+'打著名' => '打著名',
+'打著錄' => '打著录',
+'打著稱' => '打著称',
+'打著者' => '打著者',
+'打著述' => '打著述',
+'扛著' => '扛着',
+'扛著書' => '扛著书',
+'扛著作' => '扛著作',
+'扛著名' => '扛著名',
+'扛著錄' => '扛著录',
+'扛著稱' => '扛著称',
+'扛著者' => '扛著者',
+'扛著述' => '扛著述',
+'掃瞄' => '扫描',
+'掃瞄器' => '扫描仪',
+'抓著' => '抓着',
+'抓著作' => '抓著作',
+'抓著名' => '抓著名',
+'抓著錄' => '抓著录',
+'抓著稱' => '抓著称',
+'抓著者' => '抓著者',
+'抓著述' => '抓著述',
+'投機份子' => '投机分子',
+'護著' => '护着',
+'護著書' => '护著书',
+'護著作' => '护著作',
+'護著名' => '护著名',
+'護著錄' => '护著录',
+'護著稱' => '护著称',
+'護著者' => '护著者',
+'護著述' => '护著述',
+'報帳' => '报账',
+'披著' => '披着',
+'披著書' => '披著书',
+'披著作' => '披著作',
+'披著名' => '披著名',
+'披著錄' => '披著录',
+'披著稱' => '披著称',
+'披著者' => '披著者',
+'披著述' => '披著述',
+'擡著' => '抬着',
+'抬著' => '抬着',
+'抬著作' => '抬著作',
+'抬著名' => '抬著名',
+'抬著錄' => '抬著录',
+'抬著稱' => '抬著称',
+'抬著者' => '抬著者',
+'抬著述' => '抬著述',
+'抱著' => '抱着',
+'抱著作' => '抱著作',
+'抱著名' => '抱著名',
+'抱著錄' => '抱著录',
+'抱著稱' => '抱著称',
+'抱著者' => '抱著者',
+'抱著述' => '抱著述',
+'擔著' => '担着',
+'拉著' => '拉着',
+'拉著書' => '拉著书',
+'拉著作' => '拉著作',
+'拉著名' => '拉著名',
+'拉著錄' => '拉著录',
+'拉著稱' => '拉著称',
+'拉著者' => '拉著者',
+'拉著述' => '拉著述',
+'拎著' => '拎着',
+'拎著作' => '拎著作',
+'拎著名' => '拎著名',
+'拎著錄' => '拎著录',
+'拎著稱' => '拎著称',
+'拎著者' => '拎著者',
+'拎著述' => '拎著述',
+'拖著' => '拖着',
+'拖著作' => '拖著作',
+'拖著名' => '拖著名',
+'拖著錄' => '拖著录',
+'拖著稱' => '拖著称',
+'拖著者' => '拖著者',
+'拖著述' => '拖著述',
+'拼著' => '拼着',
+'拼著作' => '拼著作',
+'拼著名' => '拼著名',
+'拼著錄' => '拼著录',
+'拼著稱' => '拼著称',
+'拼著者' => '拼著者',
+'拼著述' => '拼著述',
+'拿著' => '拿着',
+'拿著作' => '拿著作',
+'拿著名' => '拿著名',
+'拿著錄' => '拿著录',
+'拿著稱' => '拿著称',
+'拿著者' => '拿著者',
+'拿著述' => '拿著述',
+'持著' => '持着',
+'持著作' => '持著作',
+'持著名' => '持著名',
+'持著錄' => '持著录',
+'持著稱' => '持著称',
+'持著者' => '持著者',
+'持著述' => '持著述',
+'掛著' => '挂着',
+'挑著' => '挑着',
+'挑著作' => '挑著作',
+'挑著名' => '挑著名',
+'挑著錄' => '挑著录',
+'挑著稱' => '挑著称',
+'挑著者' => '挑著者',
+'挑著述' => '挑著述',
+'擋著' => '挡着',
+'擋著作' => '挡著作',
+'擋著名' => '挡著名',
+'擋著錄' => '挡著录',
+'擋著稱' => '挡著称',
+'擋著者' => '挡著者',
+'擋著述' => '挡著述',
+'掙著' => '挣着',
+'掙著書' => '挣著书',
+'掙著作' => '挣著作',
+'掙著名' => '挣著名',
+'掙著錄' => '挣著录',
+'掙著稱' => '挣著称',
+'掙著者' => '挣著者',
+'掙著述' => '挣著述',
+'揮著' => '挥着',
+'揮著作' => '挥著作',
+'揮著名' => '挥著名',
+'揮著錄' => '挥著录',
+'揮著稱' => '挥著称',
+'揮著者' => '挥著者',
+'揮著述' => '挥著述',
+'挨著' => '挨着',
+'挨著作' => '挨著作',
+'挨著名' => '挨著名',
+'挨著錄' => '挨著录',
+'挨著稱' => '挨著称',
+'挨著者' => '挨著者',
+'挨著述' => '挨著述',
+'捆著' => '捆着',
+'捆著作' => '捆著作',
+'捆著名' => '捆著名',
+'捆著錄' => '捆著录',
+'捆著稱' => '捆著称',
+'捆著者' => '捆著者',
+'捆著述' => '捆著述',
+'據著' => '据着',
+'據著書' => '据著书',
+'據著作' => '据著作',
+'據著名' => '据著名',
+'據著錄' => '据著录',
+'據著稱' => '据著称',
+'據著者' => '据著者',
+'據著述' => '据著述',
'積架' => '捷豹',
-'介面' => '接口',
+'掖著' => '掖着',
+'掖著作' => '掖著作',
+'掖著名' => '掖著名',
+'掖著錄' => '掖著录',
+'掖著稱' => '掖著称',
+'掖著者' => '掖著者',
+'掖著述' => '掖著述',
+'接著' => '接着',
+'接著作' => '接著作',
+'接著名' => '接著名',
+'接著錄' => '接著录',
+'接著稱' => '接著称',
+'接著者' => '接著者',
+'接著述' => '接著述',
'控制項' => '控件',
+'揉著' => '揉着',
+'揉著書' => '揉著书',
+'揉著作' => '揉著作',
+'揉著名' => '揉著名',
+'揉著錄' => '揉著录',
+'揉著稱' => '揉著称',
+'揉著者' => '揉著者',
+'揉著述' => '揉著述',
+'提著' => '提着',
+'提著作' => '提著作',
+'提著名' => '提著名',
+'提著錄' => '提著录',
+'提著稱' => '提著称',
+'提著者' => '提著者',
+'提著述' => '提著述',
+'外掛程式' => '插件',
+'摟著' => '搂着',
+'摟著作' => '搂著作',
+'摟著名' => '搂著名',
+'摟著錄' => '搂著录',
+'摟著稱' => '搂著称',
+'摟著者' => '搂著者',
+'摟著述' => '搂著述',
+'搜尋引擎' => '搜索引擎',
+'擺著' => '摆着',
+'擺著作' => '摆著作',
+'擺著名' => '摆著名',
+'擺著錄' => '摆著录',
+'擺著稱' => '摆著称',
+'擺著者' => '摆著者',
+'擺著述' => '摆著述',
+'電單車' => '摩托车',
+'戴卓爾' => '撒切尔',
+'柴契爾' => '撒切尔',
+'撼著' => '撼着',
+'撼著書' => '撼著书',
+'撼著作' => '撼著作',
+'撼著名' => '撼著名',
+'撼著錄' => '撼著录',
+'撼著稱' => '撼著称',
+'撼著者' => '撼著者',
+'撼著述' => '撼著述',
+'作業系統' => '操作系统',
+'收帳' => '收账',
+'放著' => '放着',
+'放著作' => '放著作',
+'放著名' => '放著名',
+'放著称' => '放著称',
+'放著稱' => '放著称',
+'放帳' => '放账',
+'敞著' => '敞着',
+'敞著作' => '敞著作',
+'敞著名' => '敞著名',
+'敞著錄' => '敞著录',
+'敞著稱' => '敞著称',
+'敞著者' => '敞著者',
+'敞著述' => '敞著述',
+'數碼訊號' => '数字信号',
+'數位訊號' => '数字信号',
+'數位技術' => '数字技术',
+'數碼技術' => '数字技术',
+'數位電視' => '数字电视',
+'數碼電視' => '数字电视',
'資料庫' => '数据库',
+'數著' => '数着',
+'數位相機' => '数码相机',
+'數著作' => '数著作',
+'數著名' => '数著名',
+'數著錄' => '数著录',
+'數著稱' => '数著称',
+'數著者' => '数著者',
+'數著述' => '数著述',
'汶萊' => '文莱',
+'鬥著' => '斗着',
+'鬥著書' => '斗著书',
+'鬥著作' => '斗著作',
+'鬥著名' => '斗著名',
+'鬥著錄' => '斗著录',
+'鬥著稱' => '斗著称',
+'鬥著者' => '斗著者',
+'鬥著述' => '斗著述',
+'斥著' => '斥着',
+'斥著書' => '斥著书',
+'斥著作' => '斥著作',
+'斥著名' => '斥著名',
+'斥著錄' => '斥著录',
+'斥著稱' => '斥著称',
+'斥著者' => '斥著者',
+'斥著述' => '斥著述',
+'史達林' => '斯大林',
'史瓦濟蘭' => '斯威士兰',
'斯洛維尼亞' => '斯洛文尼亚',
+'史特勞斯' => '斯特劳斯',
+'紐幾內亞' => '新几内亚',
+'紐澤西' => '新泽西',
'紐西蘭' => '新西兰',
-'即食麵' => '方便面',
-'快速面' => '方便面',
-'泡麵' => '方便面',
-'速食麵' => '方便面',
+'舊帳' => '旧账',
+'三藩市' => '旧金山',
+'昂山素姬' => '昂山素季',
+'翁山蘇姬' => '昂山素季',
+'昂著' => '昂着',
+'昂著書' => '昂著书',
+'昂著作' => '昂著作',
+'昂著名' => '昂著名',
+'昂著錄' => '昂著录',
+'昂著稱' => '昂著称',
+'昂著者' => '昂著者',
+'昂著述' => '昂著述',
+'明白帳' => '明白账',
+'映著' => '映着',
+'映著書' => '映著书',
+'映著作' => '映著作',
+'映著名' => '映著名',
+'映著錄' => '映著录',
+'映著稱' => '映著称',
+'映著者' => '映著者',
+'映著述' => '映著述',
+'顯示卡' => '显卡',
+'显著' => '显著',
+'顯著' => '显著',
+'晃著' => '晃着',
+'晃著作' => '晃著作',
+'晃著名' => '晃著名',
+'晃著錄' => '晃著录',
+'晃著稱' => '晃著称',
+'晃著者' => '晃著者',
+'晃著述' => '晃著述',
+'普利茲' => '普利策',
+'蒲美蓬' => '普密蓬',
+'蒲朗克' => '普朗克',
+'電晶體' => '晶体管',
+'智慧型' => '智能',
+'智慧卡' => '智能卡',
+'智慧手機' => '智能手机',
+'暗著' => '暗着',
+'暗著書' => '暗著书',
+'暗著作' => '暗著作',
+'暗著名' => '暗著名',
+'暗著錄' => '暗著录',
+'暗著稱' => '暗著称',
+'暗著者' => '暗著者',
+'暗著述' => '暗著述',
+'有著' => '有着',
+'有著書' => '有著书',
+'有著作' => '有著作',
+'有著名' => '有著名',
+'有著錄' => '有著录',
+'有著稱' => '有著称',
+'有著者' => '有著者',
+'有著述' => '有著述',
'伺服器' => '服务器',
+'望著' => '望着',
+'望著作' => '望著作',
+'望著名' => '望著名',
+'望著錄' => '望著录',
+'望著稱' => '望著称',
+'望著者' => '望著者',
+'望著述' => '望著述',
+'朝著' => '朝着',
+'朝著作' => '朝著作',
+'朝著名' => '朝著名',
+'朝著錄' => '朝著录',
+'朝著稱' => '朝著称',
+'朝著者' => '朝著者',
+'朝著述' => '朝著述',
+'本份' => '本分',
+'本本份份' => '本本分分',
+'班傑明' => '本杰明',
+'本著' => '本着',
+'本著書' => '本著书',
+'本著作' => '本著作',
+'本著名' => '本著名',
+'本著錄' => '本著录',
+'本著稱' => '本著称',
+'本著者' => '本著者',
+'本著述' => '本著述',
+'本帳' => '本账',
'機械人' => '机器人',
-'機器人' => '机器人',
-'許可權' => '权限',
-'寶獅' => '标志',
+'工具機' => '机床',
+'殺著' => '杀着',
+'殺著書' => '杀著书',
+'殺著作' => '杀著作',
+'殺著名' => '杀著名',
+'殺著錄' => '杀著录',
+'殺著稱' => '杀著称',
+'殺著者' => '杀著者',
+'殺著述' => '杀著述',
+'雜著' => '杂着',
+'雜著書' => '杂著书',
+'雜著作' => '杂著作',
+'雜著名' => '杂著名',
+'雜著錄' => '杂著录',
+'雜著稱' => '杂著称',
+'雜著者' => '杂著者',
+'雜著述' => '杂著述',
+'杜塞道夫' => '杜塞尔多夫',
+'來著' => '来着',
+'來著書' => '来著书',
+'來著作' => '来著作',
+'來著名' => '来著名',
+'來著錄' => '来著录',
+'來著稱' => '来著称',
+'來著者' => '来著者',
+'來著述' => '来著述',
+'板著臉' => '板着脸',
+'枕著' => '枕着',
+'枕著作' => '枕著作',
+'枕著名' => '枕著名',
+'枕著錄' => '枕著录',
+'枕著稱' => '枕著称',
+'枕著者' => '枕著者',
+'枕著述' => '枕著述',
+'槍枝' => '枪支',
+'柏林圍牆' => '柏林墙',
+'查帳' => '查账',
+'查維茲' => '查韦斯',
+'標誌著' => '标志着',
'格瑞那達' => '格林纳达',
-'榴槤' => '榴莲',
-'榴梿' => '榴莲',
+'森巴舞' => '桑巴舞',
+'梅赫西迪' => '梅赛德斯',
+'夢著' => '梦着',
+'夢著書' => '梦著书',
+'夢著作' => '梦著作',
+'夢著名' => '梦著名',
+'夢著錄' => '梦著录',
+'夢著稱' => '梦著称',
+'夢著者' => '梦著者',
+'夢著述' => '梦著述',
+'梳著' => '梳着',
+'梳著作' => '梳著作',
+'梳著名' => '梳著名',
+'梳著錄' => '梳著录',
+'梳著稱' => '梳著称',
+'梳著者' => '梳著者',
+'梳著述' => '梳著述',
+'梵谷' => '梵高',
+'機率' => '概率',
+'欠帳' => '欠账',
+'死帳' => '死账',
+'庇里牛斯' => '比利牛斯',
+'畢卡索' => '毕加索',
'茅利塔尼亞' => '毛里塔尼亚',
'毛里裘斯' => '毛里求斯',
'模里西斯' => '毛里求斯',
-'华乐' => '民乐',
-'中樂' => '民乐',
-'永曆' => '永历',
-'沙地阿拉伯' => '沙特阿拉伯',
+'公厘' => '毫米',
+'公釐' => '毫米',
+'胺基酸' => '氨基酸',
+'水份' => '水分',
+'水氣' => '水汽',
+'求著' => '求着',
+'求著書' => '求著书',
+'求著作' => '求著作',
+'求著名' => '求著名',
+'求著錄' => '求著录',
+'求著稱' => '求著称',
+'求著者' => '求著者',
+'求著述' => '求著述',
+'漢諾瓦' => '汉诺威',
+'沈著' => '沉着',
+'沉著' => '沉着',
+'沉著書' => '沉著书',
+'沉著作' => '沉著作',
+'沉著名' => '沉著名',
+'沉著錄' => '沉著录',
+'沉著稱' => '沉著称',
+'沉著者' => '沉著者',
+'沉著述' => '沉著述',
'沙烏地阿拉伯' => '沙特阿拉伯',
+'沙地阿拉伯' => '沙特阿拉伯',
+'沿著' => '沿着',
+'沿著書' => '沿著书',
+'沿著作' => '沿著作',
+'沿著名' => '沿著名',
+'沿著錄' => '沿著录',
+'沿著稱' => '沿著称',
+'沿著者' => '沿著者',
+'沿著述' => '沿著述',
+'玻里尼西亞' => '波利尼西亚',
+'波士尼亞' => '波斯尼亚',
'波士尼亞赫塞哥維納' => '波斯尼亚和黑塞哥维那',
+'鐵達尼號' => '泰坦尼克号',
+'幫浦' => '泵',
'辛巴威' => '津巴布韦',
'宏都拉斯' => '洪都拉斯',
+'活著' => '活着',
+'活著書' => '活著书',
+'活著作' => '活著作',
+'活著名' => '活著名',
+'活著錄' => '活著录',
+'活著稱' => '活著称',
+'活著者' => '活著者',
+'活著述' => '活著述',
+'流水帳' => '流水账',
+'流著' => '流着',
+'流著書' => '流著书',
+'流著作' => '流著作',
+'流著名' => '流著名',
+'流著錄' => '流著录',
+'流著稱' => '流著称',
+'流著者' => '流著者',
+'流著述' => '流著述',
+'流露著' => '流露着',
+'浮著' => '浮着',
+'蘭卡威' => '浮罗交怡',
+'浮著書' => '浮著书',
+'浮著作' => '浮著作',
+'浮著名' => '浮著名',
+'浮著錄' => '浮著录',
+'浮著稱' => '浮著称',
+'浮著者' => '浮著者',
+'浮著述' => '浮著述',
+'海洛英' => '海洛因',
+'海浬' => '海里',
+'塗著' => '涂着',
+'潤著' => '润着',
+'潤著書' => '润著书',
+'潤著作' => '润著作',
+'潤著名' => '润著名',
+'潤著錄' => '润著录',
+'潤著稱' => '润著称',
+'潤著者' => '润著者',
+'潤著述' => '润著述',
+'涵著' => '涵着',
+'涵著書' => '涵著书',
+'涵著作' => '涵著作',
+'涵著名' => '涵著名',
+'涵著錄' => '涵著录',
+'涵著稱' => '涵著称',
+'涵著者' => '涵著者',
+'涵著述' => '涵著述',
+'混帳' => '混账',
+'清帳' => '清账',
+'渴著' => '渴着',
+'渴著書' => '渴著书',
+'渴著作' => '渴著作',
+'渴著名' => '渴著名',
+'渴著錄' => '渴著录',
+'渴著稱' => '渴著称',
+'渴著者' => '渴著者',
+'渴著述' => '渴著述',
+'原始碼' => '源代码',
+'溢著' => '溢着',
+'溢著書' => '溢著书',
+'溢著作' => '溢著作',
+'溢著名' => '溢著名',
+'溢著錄' => '溢著录',
+'溢著稱' => '溢著称',
+'溢著者' => '溢著者',
+'溢著述' => '溢著述',
'滑鼠蛇' => '滑鼠蛇',
'滿16進位' => '满16进位',
'滿二進位' => '满二进位',
@@ -18983,21 +19146,322 @@ $zh2CN = array(
'滿六進位' => '满六进位',
'滿十六進位' => '满十六进位',
'滿十進位' => '满十进位',
-'蓋火鍋' => '火锅盖帽',
+'滿著' => '满着',
+'滿著作' => '满著作',
+'滿著名' => '满著名',
+'滿著者' => '满著者',
+'演著' => '演着',
+'演著書' => '演著书',
+'演著作' => '演著作',
+'演著名' => '演著名',
+'演著錄' => '演著录',
+'演著稱' => '演著称',
+'演著者' => '演著者',
+'演著述' => '演著述',
+'漫著' => '漫着',
+'漫著書' => '漫著书',
+'漫著作' => '漫著作',
+'漫著名' => '漫著名',
+'漫著錄' => '漫著录',
+'漫著稱' => '漫著称',
+'漫著者' => '漫著者',
+'漫著述' => '漫著述',
+'雷射' => '激光',
+'點著' => '点着',
+'點著作' => '点著作',
+'點著名' => '点著名',
+'點著錄' => '点著录',
+'點著稱' => '点著称',
+'點著者' => '点著者',
+'點著述' => '点著述',
+'爛帳' => '烂账',
+'燒著' => '烧着',
+'燒著作' => '烧著作',
+'燒著名' => '烧著名',
+'燒著錄' => '烧著录',
+'燒著稱' => '烧著称',
+'燒著者' => '烧著者',
+'燒著述' => '烧著述',
+'照著' => '照着',
+'照著書' => '照著书',
+'照著作' => '照著作',
+'照著名' => '照著名',
+'照著錄' => '照著录',
+'照著稱' => '照著称',
+'照著者' => '照著者',
+'照著述' => '照著述',
+'愛護著' => '爱护着',
+'愛著' => '爱着',
+'愛著書' => '爱著书',
+'愛著作' => '爱著作',
+'愛著名' => '爱著名',
+'愛著錄' => '爱著录',
+'愛著稱' => '爱著称',
+'愛著者' => '爱著者',
+'愛著述' => '爱著述',
+'牽著' => '牵着',
+'牽著書' => '牵著书',
+'牽著作' => '牵著作',
+'牽著名' => '牵著名',
+'牽著錄' => '牵著录',
+'牽著稱' => '牵著称',
+'牽著者' => '牵著者',
+'牽著述' => '牵著述',
+'千里達' => '特立尼达',
+'千里達及托巴哥' => '特立尼达和多巴哥',
'千里達托貝哥' => '特立尼达和托巴哥',
'狗隻' => '犬只',
-'卡佩雅蒂' => '珍妮弗·卡普里亚蒂',
-'諾魯' => '瑙鲁',
+'獨著' => '独着',
+'獨著書' => '独著书',
+'獨著作' => '独著作',
+'獨著名' => '独著名',
+'獨著錄' => '独著录',
+'獨著稱' => '独著称',
+'獨著者' => '独著者',
+'獨著述' => '独著述',
+'猜著' => '猜着',
+'猜著書' => '猜着书',
+'猜著作' => '猜著作',
+'猜著名' => '猜著名',
+'猜著錄' => '猜著录',
+'猜著稱' => '猜著称',
+'猜著者' => '猜著者',
+'猜著述' => '猜著述',
+'玩著' => '玩着',
'萬那杜' => '瓦努阿图',
'溫納圖' => '瓦努阿图',
-'碟片' => '盘片',
-'短訊' => '短信',
-'簡訊' => '短信',
-'矽尘' => '矽尘',
+'華勒沙' => '瓦文萨',
+'華里沙' => '瓦文萨',
+'甜著' => '甜着',
+'甜著書' => '甜著书',
+'甜著作' => '甜著作',
+'甜著名' => '甜著名',
+'甜著錄' => '甜著录',
+'甜著稱' => '甜著称',
+'甜著者' => '甜著者',
+'甜著述' => '甜著述',
+'用著' => '用着',
+'用著書' => '用著书',
+'用著作' => '用著作',
+'用著名' => '用著名',
+'用著錄' => '用著录',
+'用著稱' => '用著称',
+'用著者' => '用著者',
+'用著述' => '用著述',
+'A型肝炎' => '甲型肝炎',
+'A肝' => '甲肝',
+'電視劇集' => '电视剧',
+'電視影集' => '电视系列剧',
+'畫著' => '画着',
+'畫著作' => '画著作',
+'畫著名' => '画著名',
+'畫著稱' => '画著称',
+'畫著者' => '画著者',
+'介面' => '界面',
+'留著' => '留着',
+'留著書' => '留着书',
+'留著作' => '留著作',
+'留著名' => '留著名',
+'留著錄' => '留著录',
+'留著稱' => '留著称',
+'留著者' => '留著者',
+'留著述' => '留著述',
+'疑著' => '疑着',
+'疑著書' => '疑著书',
+'疑著作' => '疑著作',
+'疑著名' => '疑著名',
+'疑著錄' => '疑著录',
+'疑著稱' => '疑著称',
+'疑著者' => '疑著者',
+'疑著述' => '疑著述',
+'狂牛症' => '疯牛病',
+'徵狀' => '症状',
+'百慕達' => '百慕大',
+'皮雅斯·布士南' => '皮尔斯·布鲁斯南',
+'皺著' => '皱着',
+'皺著書' => '皱著书',
+'皺著作' => '皱著作',
+'皺著名' => '皱著名',
+'皺著錄' => '皱著录',
+'皺著稱' => '皱著称',
+'皺著者' => '皱著者',
+'皺著述' => '皱著述',
+'鹽份' => '盐分',
+'盛著' => '盛着',
+'盛著書' => '盛著书',
+'盛著作' => '盛著作',
+'盛著名' => '盛著名',
+'盛著錄' => '盛著录',
+'盛著稱' => '盛著称',
+'盛著者' => '盛著者',
+'盛著述' => '盛著述',
+'盯著' => '盯着',
+'盯著書' => '盯着书',
+'盯著作' => '盯著作',
+'盯著名' => '盯著名',
+'盯著錄' => '盯著录',
+'盯著稱' => '盯著称',
+'盯著者' => '盯著者',
+'盯著述' => '盯著述',
+'盾著' => '盾着',
+'盾著書' => '盾著书',
+'盾著作' => '盾著作',
+'盾著名' => '盾著名',
+'盾著錄' => '盾著录',
+'盾著稱' => '盾著称',
+'盾著者' => '盾著者',
+'盾著述' => '盾著述',
+'看著' => '看着',
+'看著書' => '看着书',
+'看著作' => '看著作',
+'看著名' => '看著名',
+'看著錄' => '看著录',
+'看著稱' => '看著称',
+'看著者' => '看著者',
+'看著述' => '看著述',
+'著業' => '着业',
+'著絲' => '着丝',
+'著麼' => '着么',
+'著人' => '着人',
+'著甚麽' => '着什么',
+'著什麼' => '着什么',
+'著他' => '着他',
+'著令' => '着令',
+'著位' => '着位',
+'著體' => '着体',
+'著你' => '着你',
+'著便' => '着便',
+'著涼' => '着凉',
+'著力' => '着力',
+'著勁' => '着劲',
+'著號' => '着号',
+'著呢' => '着呢',
+'著哩' => '着哩',
+'著地' => '着地',
+'著墨' => '着墨',
+'著聲' => '着声',
+'著處' => '着处',
+'著她' => '着她',
+'著妳' => '着妳',
+'著姓' => '着姓',
+'著它' => '着它',
+'著定' => '着定',
+'著實' => '着实',
+'著己' => '着己',
+'著帳' => '着帐',
+'著床' => '着床',
+'著庸' => '着庸',
+'著式' => '着式',
+'著錄' => '着录',
+'著心' => '着心',
+'著志' => '着志',
+'著忙' => '着忙',
+'著急' => '着急',
+'著惱' => '着恼',
+'著驚' => '着惊',
+'著想' => '着想',
+'著意' => '着意',
+'著慌' => '着慌',
+'著我' => '着我',
+'著手' => '着手',
+'著抹' => '着抹',
+'著摸' => '着摸',
+'著撰' => '着撰',
+'著數' => '着数',
+'著明' => '着明',
+'著末' => '着末',
+'著極' => '着极',
+'著格' => '着格',
+'著棋' => '着棋',
+'著氣' => '着气',
+'著法' => '着法',
+'著淺' => '着浅',
+'著火' => '着火',
+'著然' => '着然',
+'著甚' => '着甚',
+'著生' => '着生',
+'著疑' => '着疑',
+'著白' => '着白',
+'著相' => '着相',
+'著眼' => '着眼',
+'著著' => '着着',
+'著祂' => '着祂',
+'著積' => '着积',
+'著稿' => '着稿',
+'著筆' => '着笔',
+'著籍' => '着籍',
+'著緊' => '着紧',
+'著緑' => '着緑',
+'著絆' => '着绊',
+'著績' => '着绩',
+'著緋' => '着绯',
+'著綠' => '着绿',
+'著肉' => '着肉',
+'著腳' => '着脚',
+'著艦' => '着舰',
+'著色' => '着色',
+'著節' => '着节',
+'著花' => '着花',
+'著莫' => '着莫',
+'著落' => '着落',
+'著槁' => '着藁',
+'著衣' => '着衣',
+'著裝' => '着装',
+'著要' => '着要',
+'著警' => '着警',
+'著趣' => '着趣',
+'著邊' => '着边',
+'著迷' => '着迷',
+'著跡' => '着迹',
+'著重' => '着重',
+'著録' => '着録',
+'著聞' => '着闻',
+'著陸' => '着陆',
+'著雝' => '着雝',
+'著鞭' => '着鞭',
+'著題' => '着题',
+'著魔' => '着魔',
+'睡著' => '睡着',
+'睡著書' => '睡著书',
+'睡著作' => '睡著作',
+'睡著名' => '睡著名',
+'睡著錄' => '睡著录',
+'睡著稱' => '睡著称',
+'睡著者' => '睡著者',
+'睡著述' => '睡著述',
+'瞞著' => '瞒着',
+'瞞著書' => '瞒著书',
+'瞞著作' => '瞒著作',
+'瞞著名' => '瞒著名',
+'瞞著錄' => '瞒著录',
+'瞞著稱' => '瞒著称',
+'瞞著者' => '瞒著者',
+'瞞著述' => '瞒著述',
+'瞧著' => '瞧着',
+'瞧著書' => '瞧着书',
+'瞧著作' => '瞧著作',
+'瞧著名' => '瞧著名',
+'瞧著錄' => '瞧著录',
+'瞧著稱' => '瞧著称',
+'瞧著者' => '瞧著者',
+'瞧著述' => '瞧著述',
+'瞪著' => '瞪着',
+'瞪著書' => '瞪著书',
+'瞪著作' => '瞪著作',
+'瞪著名' => '瞪著名',
+'瞪著錄' => '瞪著录',
+'瞪著稱' => '瞪著称',
+'瞪著者' => '瞪著者',
+'瞪著述' => '瞪著述',
+'智財權' => '知识产权',
+'智慧財產權' => '知识产权',
+'知識份子' => '知识分子',
+'什勒斯維希' => '石勒苏益格',
'矽塵' => '矽尘',
+'矽尘' => '矽尘',
'矽肺' => '矽肺',
-'矽钢' => '矽钢',
'矽鋼' => '矽钢',
+'矽钢' => '矽钢',
'矽' => '硅',
'矽片' => '硅片',
'矽谷' => '硅谷',
@@ -19005,81 +19469,845 @@ $zh2CN = array(
'硬碟' => '硬盘',
'磁碟' => '磁盘',
'磁軌' => '磁道',
+'福馬林' => '福尔马林',
+'福著' => '福着',
+'福著書' => '福著书',
+'福著作' => '福著作',
+'福著名' => '福著名',
+'福著錄' => '福著录',
+'福著稱' => '福著称',
+'福著者' => '福著者',
+'福著述' => '福著述',
+'私帳' => '私账',
'葛摩' => '科摩罗',
'象牙海岸' => '科特迪瓦',
+'積極份子' => '积极分子',
'行動電話' => '移动电话',
'流動電話' => '移动电话',
+'行動網路' => '移动网络',
+'流動網絡' => '移动网络',
+'程式設計師' => '程序员',
'程式控制' => '程控',
+'空中巴士' => '空中客车',
+'空氣質素' => '空气质量',
+'空氣品質' => '空气质量',
+'空著' => '空着',
+'空著書' => '空著书',
+'空著作' => '空著作',
+'空著名' => '空著名',
+'空著錄' => '空著录',
+'空著稱' => '空著称',
+'空著者' => '空著者',
+'空著述' => '空著述',
+'穿著' => '穿着',
+'穿著書' => '穿著书',
+'穿著作' => '穿著作',
+'穿著名' => '穿著名',
+'穿著錄' => '穿著录',
+'穿著稱' => '穿著称',
+'穿著者' => '穿著者',
+'穿著述' => '穿著述',
'突尼西亞' => '突尼斯',
-'谐星' => '笑星',
-'等於' => '等于',
+'豎著' => '竖着',
+'豎著書' => '竖著书',
+'豎著作' => '竖著作',
+'豎著名' => '竖著名',
+'豎著錄' => '竖著录',
+'豎著稱' => '竖著称',
+'豎著者' => '竖著者',
+'豎著述' => '竖著述',
+'站著' => '站着',
+'站著書' => '站著书',
+'站著作' => '站著作',
+'站著名' => '站著名',
+'站著錄' => '站著录',
+'站著稱' => '站著称',
+'站著者' => '站著者',
+'站著述' => '站著述',
+'笑著' => '笑着',
+'笑著書' => '笑著书',
+'笑著作' => '笑著作',
+'笑著名' => '笑著名',
+'笑著錄' => '笑著录',
+'笑著稱' => '笑著称',
+'笑著者' => '笑著者',
+'笑著述' => '笑著述',
+'筆帳' => '笔账',
+'提比里西' => '第比利斯',
+'簽著' => '签着',
+'簽帳' => '签账',
'運算元' => '算子',
'演算法' => '算法',
-'顆進球' => '粒入球',
+'算帳' => '算账',
+'管著' => '管着',
+'管著書' => '管著书',
+'管著作' => '管著作',
+'管著名' => '管著名',
+'管著錄' => '管著录',
+'管著稱' => '管著称',
+'管著者' => '管著者',
+'管著述' => '管著述',
+'管帳' => '管账',
+'公尺' => '米',
+'糊塗帳' => '糊涂账',
+'糖份' => '糖分',
+'動畫影集' => '系列动画片',
+'繫著' => '系着',
+'索贊尼辛' => '索尔仁尼琴',
+'索忍尼辛' => '索尔仁尼琴',
+'蘇辛尼津' => '索尔仁尼琴',
'索馬利亞' => '索马里',
+'索馬利蘭' => '索马里兰',
+'正體中文' => '繁体中文',
+'強斯頓環礁' => '约翰斯顿岛',
+'組份' => '组分',
+'經常帳' => '经常账',
+'經濟帳' => '经济账',
+'綁著' => '绑着',
+'綁著書' => '绑著书',
+'綁著作' => '绑著作',
+'綁著名' => '绑著名',
+'綁著錄' => '绑著录',
+'綁著稱' => '绑著称',
+'綁著者' => '绑著者',
+'綁著述' => '绑著述',
+'結帳' => '结账',
+'繞著' => '绕着',
+'繞著書' => '绕著书',
+'繞著作' => '绕著作',
+'繞著名' => '绕著名',
+'繞著錄' => '绕著录',
+'繞著稱' => '绕著称',
+'繞著者' => '绕著者',
+'繞著述' => '绕著述',
+'維根斯坦' => '维特根斯坦',
+'繃著' => '绷着',
+'緣份' => '缘分',
+'纏著' => '缠着',
+'纏著書' => '缠著书',
+'纏著作' => '缠著作',
+'纏著名' => '缠著名',
+'纏著錄' => '缠著录',
+'纏著稱' => '缠著称',
+'纏著者' => '缠著者',
+'纏著述' => '缠著述',
+'網站連結' => '网站链接',
'網路' => '网络',
-'網絡' => '网络',
+'網頁連結' => '网页链接',
+'罩著' => '罩着',
+'罩著書' => '罩著书',
+'罩著作' => '罩著作',
+'罩著名' => '罩著名',
+'罩著錄' => '罩著录',
+'罩著稱' => '罩著称',
+'罩著者' => '罩著者',
+'罩著述' => '罩著述',
+'美著' => '美着',
+'美著書' => '美著书',
+'美著作' => '美著作',
+'美著名' => '美著名',
+'美著錄' => '美著录',
+'美著稱' => '美著称',
+'美著者' => '美著者',
+'美著述' => '美著述',
+'耀著' => '耀着',
+'耀著書' => '耀著书',
+'耀著作' => '耀著作',
+'耀著名' => '耀著名',
+'耀著錄' => '耀著录',
+'耀著稱' => '耀著称',
+'耀著者' => '耀著者',
+'耀著述' => '耀著述',
'寮國' => '老挝',
-'肯雅' => '肯尼亚',
-'肯亞' => '肯尼亚',
-'單車' => '自行车',
+'寮人民民主共和國' => '老挝人民民主共和国',
+'寮語' => '老挝语',
+'考著' => '考着',
+'考著書' => '考著书',
+'考著作' => '考著作',
+'考著名' => '考著名',
+'考著錄' => '考著录',
+'考著稱' => '考著称',
+'考著者' => '考著者',
+'考著述' => '考著述',
+'職份' => '职分',
+'辛康納利' => '肖恩·康纳利',
+'蕭士塔高維奇' => '肖斯塔科维奇',
+'蕭士達高維契' => '肖斯塔科维奇',
+'甘迺迪' => '肯尼迪',
+'背著' => '背着',
+'背著書' => '背著书',
+'背著作' => '背著作',
+'背著名' => '背著名',
+'背著錄' => '背著录',
+'背著稱' => '背著称',
+'背著者' => '背著者',
+'背著述' => '背著述',
+'膠著' => '胶着',
+'膠著書' => '胶著书',
+'膠著作' => '胶著作',
+'膠著名' => '胶著名',
+'膠著錄' => '胶著录',
+'膠著稱' => '胶著称',
+'膠著者' => '胶著者',
+'膠著述' => '胶著述',
+'舒麥加' => '舒马赫',
'太空梭' => '航天飞机',
'穿梭機' => '航天飞机',
-'節慶' => '节日',
-'晶元' => '芯片',
+'藝著' => '艺着',
+'藝著書' => '艺著书',
+'藝著作' => '艺著作',
+'藝著名' => '艺著名',
+'藝著錄' => '艺著录',
+'藝著稱' => '艺著称',
+'藝著者' => '艺著者',
+'藝著述' => '艺著述',
+'愛滋' => '艾滋',
'晶片' => '芯片',
+'晶元' => '芯片',
'蘇利南' => '苏里南',
+'苦著' => '苦着',
+'苦著書' => '苦著书',
+'苦著作' => '苦著作',
+'苦著名' => '苦著名',
+'苦著錄' => '苦著录',
+'苦著稱' => '苦著称',
+'苦著者' => '苦著者',
+'苦著述' => '苦著述',
+'英吋' => '英寸',
+'英呎' => '英尺',
+'大英國協' => '英联邦',
+'共和联邦' => '英联邦',
'士多啤梨' => '草莓',
+'螢光棒' => '荧光棒',
+'螢屏' => '荧屏',
+'霍爾斯坦' => '荷尔斯泰因',
'莫三比克' => '莫桑比克',
+'雷伊泰灣' => '莱特湾',
'賴索托' => '莱索托',
+'穫著' => '获着',
+'獲著' => '获着',
+'獲著書' => '获著书',
+'獲著作' => '获著作',
+'獲著名' => '获著名',
+'獲著錄' => '获著录',
+'獲著稱' => '获著称',
+'獲著者' => '获著者',
+'獲著述' => '获著述',
+'塞拉耶佛' => '萨拉热窝',
+'落著' => '落着',
+'落著書' => '落著书',
+'落著作' => '落著作',
+'落著名' => '落著名',
+'落著錄' => '落著录',
+'落著稱' => '落著称',
+'落著者' => '落著者',
+'落著述' => '落著述',
+'滿地可' => '蒙特利尔',
+'蒙特婁' => '蒙特利尔',
+'蒙著' => '蒙着',
+'蒙著書' => '蒙著书',
+'蒙著作' => '蒙著作',
+'蒙著名' => '蒙著名',
+'蒙著錄' => '蒙著录',
+'蒙著稱' => '蒙著称',
+'蒙著者' => '蒙著者',
+'蒙著述' => '蒙著述',
+'蓋著' => '蓋着',
+'藍芽' => '蓝牙',
+'薛丁格' => '薛定谔',
+'藏著' => '藏着',
+'藏著書' => '藏著书',
+'藏著作' => '藏著作',
+'藏著名' => '藏著名',
+'藏著錄' => '藏著录',
+'藏著稱' => '藏著称',
+'藏著者' => '藏著者',
+'藏著述' => '藏著述',
+'蘸著' => '蘸着',
+'蘸著書' => '蘸著书',
+'蘸著作' => '蘸著作',
+'蘸著名' => '蘸著名',
+'蘸著錄' => '蘸著录',
+'蘸著稱' => '蘸著称',
+'蘸著者' => '蘸著者',
+'蘸著述' => '蘸著述',
+'行著' => '行着',
+'行著書' => '行著书',
+'行著作' => '行著作',
+'行著名' => '行著名',
+'行著錄' => '行著录',
+'行著稱' => '行著称',
+'行著者' => '行著者',
+'行著述' => '行著述',
+'衣著' => '衣着',
+'衣著書' => '衣著书',
+'衣著作' => '衣著作',
+'衣著名' => '衣著名',
+'衣著錄' => '衣著录',
+'衣著稱' => '衣著称',
+'衣著者' => '衣著者',
+'衣著述' => '衣著述',
+'表姊' => '表姐',
+'裝著' => '装着',
+'裝著書' => '装著书',
+'裝著作' => '装著作',
+'裝著名' => '装著名',
+'裝著錄' => '装著录',
+'裝著稱' => '装著称',
+'裝著者' => '装著者',
+'裝著述' => '装著述',
+'裹著' => '裹着',
+'裹著書' => '裹著书',
+'裹著作' => '裹著作',
+'裹著名' => '裹著名',
+'裹著錄' => '裹著录',
+'裹著稱' => '裹著称',
+'裹著者' => '裹著者',
+'裹著述' => '裹著述',
+'要帳' => '要账',
+'覆著' => '覆着',
+'見著' => '见着',
+'見著書' => '见著书',
+'見著作' => '见著作',
+'見著名' => '见著名',
+'見著錄' => '见著录',
+'見著稱' => '见著称',
+'見著者' => '见著者',
+'見著述' => '见著述',
+'占士邦' => '詹姆斯·邦德',
+'警戒著' => '警戒着',
+'計畫' => '计划',
+'電腦程式' => '计算机程序',
+'認帳' => '认账',
+'記著' => '记着',
+'記著書' => '记著书',
+'記著作' => '记著作',
+'記著名' => '记著名',
+'記著錄' => '记著录',
+'記著稱' => '记著称',
+'記著者' => '记著者',
+'記著述' => '记著述',
+'記帳' => '记账',
'辭彙' => '词汇',
'片語' => '词组',
-'調制解調器' => '调制解调器',
+'試著' => '试着',
+'試著書' => '试著书',
+'試著作' => '试著作',
+'試著名' => '试著名',
+'試著錄' => '试著录',
+'試著稱' => '试著称',
+'試著者' => '试著者',
+'試著述' => '试著述',
+'語著' => '语着',
+'語著書' => '语著书',
+'語著作' => '语著作',
+'語著名' => '语著名',
+'語著錄' => '语著录',
+'語著稱' => '语著称',
+'語著者' => '语著者',
+'語著述' => '语著述',
+'說著' => '说着',
+'說著作' => '说著作',
+'說著稱' => '说著称',
+'說著者' => '说著者',
+'說著述' => '说著述',
+'諾曼第' => '诺曼底',
'數據機' => '调制解调器',
-'貝南' => '贝宁',
+'象徵著' => '象征着',
+'象徵著名' => '象征著名',
+'豫著' => '豫着',
+'豫著書' => '豫著书',
+'豫著作' => '豫著作',
+'豫著名' => '豫著名',
+'豫著錄' => '豫著录',
+'豫著稱' => '豫著称',
+'豫著者' => '豫著者',
+'豫著述' => '豫著述',
+'碧咸' => '贝克汉姆',
+'貝爾格勒' => '贝尔格莱德',
+'貞著' => '贞着',
+'貞著書' => '贞著书',
+'貞著作' => '贞著作',
+'貞著名' => '贞著名',
+'貞著錄' => '贞著录',
+'貞著稱' => '贞著称',
+'貞著者' => '贞著者',
+'貞著述' => '贞著述',
+'負著' => '负着',
+'帳上' => '账上',
+'帳冊' => '账册',
+'帳務' => '账务',
+'帳單' => '账单',
+'帳號' => '账号',
+'帳外' => '账外',
+'帳戶' => '账户',
+'帳房' => '账房',
+'帳本' => '账本',
+'帳款' => '账款',
+'帳目' => '账目',
+'帳簿' => '账簿',
+'帳面' => '账面',
+'賒帳' => '赊账',
+'賴帳' => '赖账',
'尚比亞' => '赞比亚',
-'绑紧跳' => '蹦极跳',
+'西臺人' => '赫梯人',
+'西臺國' => '赫梯国',
+'西臺帝' => '赫梯帝',
+'西臺文' => '赫梯文',
+'西臺族' => '赫梯族',
+'西臺王' => '赫梯王',
+'西臺語' => '赫梯语',
+'赫魯雪夫' => '赫鲁晓夫',
+'走為上著' => '走为上着',
+'走著' => '走着',
+'走著書' => '走著书',
+'走著作' => '走著作',
+'走著名' => '走著名',
+'走著錄' => '走著录',
+'走著稱' => '走著称',
+'走著者' => '走著者',
+'走著述' => '走著述',
+'趕著' => '赶着',
+'趕著書' => '赶著书',
+'趕著作' => '赶著作',
+'趕著名' => '赶著名',
+'趕著錄' => '赶著录',
+'趕著稱' => '赶著称',
+'趕著者' => '赶著者',
+'趕著述' => '赶著述',
+'超連結' => '超链接',
+'趴著' => '趴着',
+'趴著書' => '趴著书',
+'趴著作' => '趴著作',
+'趴著名' => '趴著名',
+'趴著錄' => '趴著录',
+'趴著稱' => '趴著称',
+'趴著者' => '趴著者',
+'趴著述' => '趴著述',
+'躍著' => '跃着',
+'躍著書' => '跃著书',
+'躍著作' => '跃著作',
+'躍著名' => '跃著名',
+'躍著錄' => '跃著录',
+'躍著稱' => '跃著称',
+'躍著者' => '跃著者',
+'躍著述' => '跃著述',
+'跑著' => '跑着',
+'跑著書' => '跑著书',
+'跑著作' => '跑著作',
+'跑著名' => '跑著名',
+'跑著錄' => '跑著录',
+'跑著稱' => '跑著称',
+'跑著者' => '跑著者',
+'跑著述' => '跑著述',
+'跟著' => '跟着',
+'跟著書' => '跟著书',
+'跟著作' => '跟著作',
+'跟著名' => '跟著名',
+'跟著錄' => '跟著录',
+'跟著稱' => '跟著称',
+'跟著者' => '跟著者',
+'跟著述' => '跟著述',
+'跪著' => '跪着',
+'跪著書' => '跪著书',
+'跪著作' => '跪著作',
+'跪著名' => '跪著名',
+'跪著錄' => '跪著录',
+'跪著稱' => '跪著称',
+'跪著者' => '跪著者',
+'跪著述' => '跪著述',
+'跳著' => '跳着',
+'跳著書' => '跳著书',
+'跳著作' => '跳著作',
+'跳著名' => '跳著名',
+'跳著錄' => '跳著录',
+'跳著稱' => '跳著称',
+'跳著者' => '跳著者',
+'跳著述' => '跳著述',
+'踏著' => '踏着',
+'踏著書' => '踏著书',
+'踏著作' => '踏著作',
+'踏著名' => '踏著名',
+'踏著錄' => '踏著录',
+'踏著稱' => '踏著称',
+'踏著者' => '踏著者',
+'踏著述' => '踏著述',
+'踩著' => '踩着',
+'踩著書' => '踩著书',
+'踩著作' => '踩著作',
+'踩著名' => '踩著名',
+'踩著錄' => '踩著录',
+'踩著稱' => '踩著称',
+'踩著者' => '踩著者',
+'踩著述' => '踩著述',
'笨豬跳' => '蹦极跳',
+'绑紧跳' => '蹦极跳',
+'身分' => '身份',
+'身著' => '身着',
+'身著書' => '身著书',
+'身著作' => '身著作',
+'身著名' => '身著名',
+'身著錄' => '身著录',
+'身著稱' => '身著称',
+'身著者' => '身著者',
+'身著述' => '身著述',
+'躺著' => '躺着',
+'躺著書' => '躺著书',
+'躺著作' => '躺著作',
+'躺著名' => '躺著名',
+'躺著錄' => '躺著录',
+'躺著稱' => '躺著称',
+'躺著者' => '躺著者',
+'躺著述' => '躺著述',
+'轉著' => '转着',
+'轉著書' => '转著书',
+'轉著作' => '转著作',
+'轉著名' => '转著名',
+'轉著錄' => '转著录',
+'轉著稱' => '转著称',
+'轉著者' => '转著者',
+'轉著述' => '转著述',
+'轉帳' => '转账',
'軟體' => '软件',
-'軟件' => '软件',
+'軟體動物' => '软体动物',
+'軟體家具' => '软体家具',
'軟碟機' => '软驱',
-'米高奧雲' => '迈克尔·欧文',
-'舒麥加' => '迈克尔·舒马赫',
-'遠程控制' => '远程控制',
-'远程控制' => '远程控制',
+'載著' => '载着',
+'載著書' => '载著书',
+'載著作' => '载著作',
+'載著名' => '载著名',
+'載著錄' => '载著录',
+'載著稱' => '载著称',
+'載著者' => '载著者',
+'載著述' => '载著述',
+'達·文西' => '达·芬奇',
+'達著' => '达着',
+'三蘭港' => '达累斯萨拉姆',
+'達文西' => '达芬奇',
+'達著書' => '达著书',
+'達著作' => '达著作',
+'達著名' => '达著名',
+'達著錄' => '达著录',
+'達著稱' => '达著称',
+'達著者' => '达著者',
+'達著述' => '达著述',
+'過份' => '过分',
+'過著' => '过着',
+'過著作' => '过著作',
+'過著名' => '过著名',
+'過著錄' => '过著录',
+'過著稱' => '过著称',
+'過著者' => '过著者',
+'過著述' => '过著述',
+'米高·奧雲' => '迈克尔·欧文',
+'還帳' => '还账',
+'演化論' => '进化论',
+'進帳' => '进账',
+'遠著' => '远着',
+'遠著書' => '远著书',
+'遠著作' => '远著作',
+'遠著名' => '远著名',
+'遠著錄' => '远著录',
+'遠著稱' => '远著称',
+'遠著者' => '远著者',
+'遠著述' => '远著述',
+'連著' => '连着',
+'連結他' => '连结他',
+'連著書' => '连著书',
+'連著作' => '连著作',
+'連著名' => '连著名',
+'連著錄' => '连著录',
+'連著稱' => '连著称',
+'連著者' => '连著者',
+'連著述' => '连著述',
+'杜拜' => '迪拜',
+'迫著' => '迫着',
+'疊代' => '迭代',
+'追著' => '追着',
+'追著書' => '追著书',
+'追著作' => '追著作',
+'追著名' => '追著名',
+'追著錄' => '追著录',
+'追著稱' => '追著称',
+'追著者' => '追著者',
+'追著述' => '追著述',
+'逆著' => '逆着',
+'逆著書' => '逆著书',
+'逆著作' => '逆著作',
+'逆著名' => '逆著名',
+'逆著錄' => '逆著录',
+'逆著稱' => '逆著称',
+'逆著者' => '逆著者',
+'逆著述' => '逆著述',
+'逼著' => '逼着',
+'逼著書' => '逼著书',
+'逼著作' => '逼著作',
+'逼著名' => '逼著名',
+'逼著錄' => '逼著录',
+'逼著稱' => '逼著称',
+'逼著者' => '逼著者',
+'逼著述' => '逼著述',
+'遇著' => '遇着',
+'遇著書' => '遇著书',
+'遇著作' => '遇著作',
+'遇著名' => '遇著名',
+'遇著錄' => '遇著录',
+'遇著稱' => '遇著称',
+'遇著者' => '遇著者',
+'遇著述' => '遇著述',
+'部份' => '部分',
+'配合著' => '配合着',
+'配合著名' => '配合著名',
+'配著' => '配着',
+'配著書' => '配著书',
+'配著作' => '配著作',
+'配著名' => '配著名',
+'配著錄' => '配著录',
+'配著稱' => '配著称',
+'配著者' => '配著者',
+'配著述' => '配著述',
+'釀著' => '酿着',
+'釀著書' => '酿著书',
+'釀著作' => '酿著作',
+'釀著名' => '酿著名',
+'釀著錄' => '酿著录',
+'釀著稱' => '酿著称',
+'釀著者' => '酿著者',
+'釀著述' => '酿著述',
+'黎克特制' => '里氏',
+'芮氏0' => '里氏0',
+'芮氏1' => '里氏1',
+'芮氏2' => '里氏2',
+'芮氏3' => '里氏3',
+'芮氏4' => '里氏4',
+'芮氏5' => '里氏5',
+'芮氏6' => '里氏6',
+'芮氏7' => '里氏7',
+'芮氏8' => '里氏8',
+'芮氏9' => '里氏9',
+'芮氏地震規模' => '里氏地震规模',
+'芮氏規模' => '里氏震级',
+'金夏沙' => '金沙萨',
+'鈽' => '钚',
+'鍅' => '钫',
+'狄托' => '铁托',
+'鋪著' => '铺着',
+'鋪著書' => '铺著书',
+'鋪著作' => '铺著作',
+'鋪著名' => '铺著名',
+'鋪著錄' => '铺著录',
+'鋪著稱' => '铺著称',
+'鋪著者' => '铺著者',
+'鋪著述' => '铺著述',
+'銷帳' => '销账',
+'鉲' => '锎',
+'鎝' => '锝',
+'鉳' => '锫',
+'鑀' => '锿',
+'鋂' => '镅',
+'錼' => '镎',
+'孟德爾頌' => '门德尔松',
+'孟德爾遜' => '门德尔松',
+'快閃記憶體' => '闪存',
+'閉著' => '闭着',
+'閉著書' => '闭著书',
+'閉著作' => '闭著作',
+'閉著名' => '闭著名',
+'閉著錄' => '闭著录',
+'閉著稱' => '闭著称',
+'閉著者' => '闭著者',
+'閉著述' => '闭著述',
+'閒著' => '闲着',
+'閑著' => '闲着',
+'閑著書' => '闲著书',
+'閑著作' => '闲著作',
+'閑著名' => '闲著名',
+'閑著錄' => '闲著录',
+'閑著稱' => '闲著称',
+'閑著者' => '闲著者',
+'閑著述' => '闲著述',
+'悶著' => '闷着',
+'鬧著' => '闹着',
+'聞著' => '闻着',
'亞塞拜然' => '阿塞拜疆',
+'阿布達比' => '阿布扎比',
'阿拉伯聯合大公國' => '阿拉伯联合酋长国',
-'散钱' => '零钱',
+'亞斯文' => '阿斯旺',
+'附著' => '附着',
+'附著書' => '附著书',
+'附著作' => '附著作',
+'附著名' => '附著名',
+'附著錄' => '附著录',
+'附著稱' => '附著称',
+'附著者' => '附著者',
+'附著述' => '附著述',
+'陋著' => '陋着',
+'陋著書' => '陋著书',
+'陋著作' => '陋著作',
+'陋著名' => '陋著名',
+'陋著錄' => '陋著录',
+'陋著稱' => '陋著称',
+'陋著者' => '陋著者',
+'陋著述' => '陋著述',
+'陪著' => '陪着',
+'陪著書' => '陪著书',
+'陪著作' => '陪著作',
+'陪著名' => '陪著名',
+'陪著錄' => '陪著录',
+'陪著稱' => '陪著称',
+'陪著者' => '陪著者',
+'陪著述' => '陪著述',
+'隨著' => '随着',
+'隨著書' => '随著书',
+'隨著作' => '随著作',
+'隨著名' => '随著名',
+'隨著錄' => '随著录',
+'隨著稱' => '随著称',
+'隨著者' => '随著者',
+'隨著述' => '随著述',
+'私隱' => '隐私',
+'隔著' => '隔着',
+'隔著書' => '隔著书',
+'隔著作' => '隔著作',
+'隔著名' => '隔著名',
+'隔著錄' => '隔著录',
+'隔著稱' => '隔著称',
+'隔著者' => '隔著者',
+'隔著述' => '隔著述',
+'耶加達' => '雅加达',
+'雅爾達' => '雅尔塔',
+'雅著' => '雅着',
+'雅著書' => '雅著书',
+'雅著作' => '雅著作',
+'雅著名' => '雅著名',
+'雅著錄' => '雅著录',
+'雅著稱' => '雅著称',
+'雅著者' => '雅著者',
+'雅著述' => '雅著述',
+'山葉' => '雅马哈',
+'雷諾瓦' => '雷诺阿',
+'荷姆茲' => '霍尔木兹',
+'非份' => '非分',
+'靠著' => '靠着',
+'靠著作' => '靠著作',
+'靠著名' => '靠著名',
+'靠著錄' => '靠著录',
+'靠著稱' => '靠著称',
+'靠著者' => '靠著者',
+'靠著述' => '靠著述',
'南韓' => '韩国',
+'音樂錄影帶' => '音乐录影带',
+'頂著' => '顶着',
+'頂著書' => '顶著书',
+'頂著作' => '顶著作',
+'頂著名' => '顶著名',
+'頂著錄' => '顶著录',
+'頂著稱' => '顶著称',
+'頂著者' => '顶著者',
+'頂著述' => '顶著述',
+'順著' => '顺着',
+'順著書' => '顺著书',
+'順著作' => '顺著作',
+'順著名' => '顺著名',
+'順著錄' => '顺著录',
+'順著稱' => '顺著称',
+'順著者' => '顺著者',
+'順著述' => '顺著述',
+'領著' => '领着',
+'領著書' => '领著书',
+'領著作' => '领著作',
+'領著名' => '领著名',
+'領著錄' => '领著录',
+'領著稱' => '领著称',
+'領著者' => '领著者',
+'領著述' => '领著述',
+'飄著' => '飘着',
+'飃著' => '飘着',
+'飄著書' => '飘著书',
+'飄著作' => '飘著作',
+'飄著名' => '飘著名',
+'飄著錄' => '飘著录',
+'飄著稱' => '飘著称',
+'飄著者' => '飘著者',
+'飄著述' => '飘著述',
+'行政總裁' => '首席执行官',
+'執行長、' => '首席执行官、',
+'執行長。' => '首席执行官。',
+'執行長,' => '首席执行官,',
+'財務長、' => '首席财务官、',
+'財務長。' => '首席财务官。',
+'財務長,' => '首席财务官,',
+'營運長、' => '首席运营官、',
+'營運長。' => '首席运营官。',
+'營運長,' => '首席运营官,',
'馬爾地夫' => '马尔代夫',
-'沙芬' => '马拉特·萨芬',
+'萌島' => '马恩岛',
+'馬拉威' => '马拉维',
+'馬斯垂克' => '马斯特里赫特',
'馬爾他' => '马耳他',
-'萬事得' => '马自达',
+'麻薩諸塞' => '马萨诸塞',
'馬利共和國' => '马里共和国',
-'預設' => '默认',
+'駛著' => '驶着',
+'駕著' => '驾着',
+'駕著書' => '驾著书',
+'駕著作' => '驾著作',
+'駕著名' => '驾著名',
+'駕著錄' => '驾著录',
+'駕著稱' => '驾著称',
+'駕著者' => '驾著者',
+'駕著述' => '驾著述',
+'罵著' => '骂着',
+'罵著書' => '骂著书',
+'罵著作' => '骂著作',
+'罵著名' => '骂著名',
+'罵著錄' => '骂著录',
+'罵著稱' => '骂著称',
+'罵著者' => '骂著者',
+'罵著述' => '骂著述',
+'騎著' => '骑着',
+'騎著書' => '骑著书',
+'騎著作' => '骑著作',
+'騎著名' => '骑著名',
+'騎著錄' => '骑著录',
+'騎著稱' => '骑著称',
+'騎著者' => '骑著者',
+'騎著述' => '骑著述',
+'騙著' => '骗着',
+'騙著書' => '骗著书',
+'騙著作' => '骗著作',
+'騙著名' => '骗著名',
+'騙著錄' => '骗著录',
+'騙著稱' => '骗著称',
+'騙著者' => '骗著者',
+'騙著述' => '骗著述',
+'尖峰時段' => '高峰时段',
+'尖峰時間' => '高峰时间',
+'高畫質' => '高清',
+'高著' => '高着',
+'高著書' => '高著书',
+'高著作' => '高著作',
+'高著名' => '高著名',
+'高著錄' => '高著录',
+'高著稱' => '高著称',
+'高著者' => '高著者',
+'高著述' => '高著述',
+'髭著' => '髭着',
+'髭著書' => '髭著书',
+'髭著作' => '髭著作',
+'髭著名' => '髭著名',
+'髭著錄' => '髭著录',
+'髭著稱' => '髭著称',
+'髭著者' => '髭著者',
+'髭著述' => '髭著述',
+'咪高峰' => '麦克风',
+'黏著' => '黏着',
+'黏著書' => '黏著书',
+'黏著作' => '黏著作',
+'黏著名' => '黏著名',
+'黏著錄' => '黏著录',
+'黏著稱' => '黏著称',
+'黏著者' => '黏著者',
+'黏著述' => '黏著述',
+'蒙特內哥羅' => '黑山',
'滑鼠' => '鼠标',
);
-
-$zh2SG = array(
-'『' => '‘',
-'』' => '’',
-'「' => '“',
-'」' => '”',
-'住房' => '住屋',
-'凉菜' => '冷菜',
-'冷盤' => '冷菜',
-'民乐' => '华乐',
-'夜校' => '夜学',
-'房价' => '屋价',
-'即食麵' => '快速面',
-'速食麵' => '快速面',
-'泡麵' => '快速面',
-'方便面' => '快速面',
-'零钱' => '散钱',
-'散紙' => '散钱',
-'榴莲' => '榴梿',
-'榴蓮' => '榴梿',
-'笨豬跳' => '绑紧跳',
-'蹦极跳' => '绑紧跳',
-'笑星' => '谐星',
-);
diff --git a/includes/actions/Action.php b/includes/actions/Action.php
index 8d11d901..bb6a4d5d 100644
--- a/includes/actions/Action.php
+++ b/includes/actions/Action.php
@@ -132,6 +132,8 @@ abstract class Action {
if ( $actionName === 'historysubmit' ) {
if ( $request->getBool( 'revisiondelete' ) ) {
$actionName = 'revisiondelete';
+ } elseif ( $request->getBool( 'editchangetags' ) ) {
+ $actionName = 'editchangetags';
} else {
$actionName = 'view';
}
@@ -375,6 +377,28 @@ abstract class Action {
}
/**
+ * Adds help link with an icon via page indicators.
+ * Link target can be overridden by a local message containing a wikilink:
+ * the message key is: lowercase action name + '-helppage'.
+ * @param string $to Target MediaWiki.org page title or encoded URL.
+ * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o.
+ * @since 1.25
+ */
+ public function addHelpLink( $to, $overrideBaseUrl = false ) {
+ global $wgContLang;
+ $msg = wfMessage( $wgContLang->lc(
+ Action::getActionName( $this->getContext() )
+ ) . '-helppage' );
+
+ if ( !$msg->isDisabled() ) {
+ $helpUrl = Skin::makeUrl( $msg->plain() );
+ $this->getOutput()->addHelpLink( $helpUrl, true );
+ } else {
+ $this->getOutput()->addHelpLink( $to, $overrideBaseUrl );
+ }
+ }
+
+ /**
* 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.
diff --git a/includes/actions/CreditsAction.php b/includes/actions/CreditsAction.php
index e064aab4..c19e8fa3 100644
--- a/includes/actions/CreditsAction.php
+++ b/includes/actions/CreditsAction.php
@@ -42,7 +42,6 @@ class CreditsAction extends FormlessAction {
* @return string HTML
*/
public function onView() {
- wfProfileIn( __METHOD__ );
if ( $this->page->getID() == 0 ) {
$s = $this->msg( 'nocredits' )->parse();
@@ -50,8 +49,6 @@ class CreditsAction extends FormlessAction {
$s = $this->getCredits( -1 );
}
- wfProfileOut( __METHOD__ );
-
return Html::rawElement( 'div', array( 'id' => 'mw-credits' ), $s );
}
@@ -63,7 +60,6 @@ class CreditsAction extends FormlessAction {
* @return string Html
*/
public function getCredits( $cnt, $showIfMax = true ) {
- wfProfileIn( __METHOD__ );
$s = '';
if ( $cnt != 0 ) {
@@ -73,8 +69,6 @@ class CreditsAction extends FormlessAction {
}
}
- wfProfileOut( __METHOD__ );
-
return $s;
}
diff --git a/includes/actions/DeleteAction.php b/includes/actions/DeleteAction.php
index 12f0dff0..be21a6f1 100644
--- a/includes/actions/DeleteAction.php
+++ b/includes/actions/DeleteAction.php
@@ -41,13 +41,14 @@ class DeleteAction extends FormlessAction {
}
public function show() {
+ $out = $this->getOutput();
if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
- $out = $this->getOutput();
$out->addModuleStyles( array(
'mediawiki.ui.input',
'mediawiki.ui.checkbox',
) );
}
+ $this->addHelpLink( 'Help:Sysop deleting and undeleting' );
$this->page->delete();
}
}
diff --git a/includes/actions/EditAction.php b/includes/actions/EditAction.php
index 88767244..6c8440ac 100644
--- a/includes/actions/EditAction.php
+++ b/includes/actions/EditAction.php
@@ -51,7 +51,7 @@ class EditAction extends FormlessAction {
$page = $this->page;
$user = $this->getUser();
- if ( wfRunHooks( 'CustomEditor', array( $page, $user ) ) ) {
+ if ( Hooks::run( 'CustomEditor', array( $page, $user ) ) ) {
$editor = new EditPage( $page );
$editor->edit();
}
diff --git a/includes/actions/FormAction.php b/includes/actions/FormAction.php
index 4c9e85dd..26f43cb0 100644
--- a/includes/actions/FormAction.php
+++ b/includes/actions/FormAction.php
@@ -63,7 +63,7 @@ abstract class FormAction extends Action {
$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 ) );
+ Hooks::run( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
$form = new HTMLForm( $this->fields, $this->getContext(), $this->getName() );
$form->setSubmitCallback( array( $this, 'onSubmit' ) );
@@ -81,7 +81,7 @@ abstract class FormAction extends Action {
$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 ) );
+ Hooks::run( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
return $form;
}
diff --git a/includes/actions/HistoryAction.php b/includes/actions/HistoryAction.php
index 8522e560..dcd77415 100644
--- a/includes/actions/HistoryAction.php
+++ b/includes/actions/HistoryAction.php
@@ -67,8 +67,7 @@ class HistoryAction extends FormlessAction {
}
/**
- * Get the Article object we are working on.
- * @return Page
+ * @return WikiPage|Article|ImagePage|CategoryPage|Page The Article object we are working on.
*/
public function getArticle() {
return $this->page;
@@ -102,8 +101,6 @@ class HistoryAction extends FormlessAction {
return; // Client cache fresh and headers sent, nothing more to do.
}
- wfProfileIn( __METHOD__ );
-
$this->preCacheMessages();
$config = $this->context->getConfig();
@@ -131,7 +128,6 @@ class HistoryAction extends FormlessAction {
$feedType = $request->getVal( 'feed' );
if ( $feedType ) {
$this->feed( $feedType );
- wfProfileOut( __METHOD__ );
return;
}
@@ -151,7 +147,6 @@ class HistoryAction extends FormlessAction {
'msgKey' => array( 'moveddeleted-notice' )
)
);
- wfProfileOut( __METHOD__ );
return;
}
@@ -196,11 +191,15 @@ class HistoryAction extends FormlessAction {
) . '&#160;' .
( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
$checkDeleted .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n" .
+ Html::submitButton(
+ $this->msg( 'allpagessubmit' )->text(),
+ array(),
+ array( 'mw-ui-progressive' )
+ ) . "\n" .
'</fieldset></form>'
);
- wfRunHooks( 'PageHistoryBeforeList', array( &$this->page, $this->getContext() ) );
+ Hooks::run( 'PageHistoryBeforeList', array( &$this->page, $this->getContext() ) );
// Create and output the list.
$pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
@@ -211,7 +210,6 @@ class HistoryAction extends FormlessAction {
);
$out->preventClickjacking( $pager->getPreventClickjacking() );
- wfProfileOut( __METHOD__ );
}
/**
@@ -416,7 +414,7 @@ class HistoryPager extends ReverseChronologicalPager {
$queryInfo['options'],
$this->tagFilter
);
- wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
+ Hooks::run( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
return $queryInfo;
}
@@ -481,20 +479,28 @@ class HistoryPager extends ReverseChronologicalPager {
'id' => 'mw-history-compare' ) ) . "\n";
$s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n";
$s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
+ $s .= Html::hidden( 'type', 'revision' ) . "\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';
- }
+ $attrs = array( 'class' => $className )
+ + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' );
$this->buttons .= $this->submitButton( $this->msg( 'compareselectedversions' )->text(),
- array( 'class' => $className )
- + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' )
+ $attrs
) . "\n";
- if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
- $this->buttons .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
+ $user = $this->getUser();
+ $actionButtons = '';
+ if ( $user->isAllowed( 'deleterevision' ) ) {
+ $actionButtons .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
+ }
+ if ( ChangeTags::showTagEditingUI( $user ) ) {
+ $actionButtons .= $this->getRevisionButton( 'editchangetags', 'history-edit-tags' );
+ }
+ if ( $actionButtons ) {
+ $this->buttons .= Xml::tags( 'div', array( 'class' =>
+ 'mw-history-revisionactions' ), $actionButtons );
}
$this->buttons .= '</div>';
@@ -561,7 +567,7 @@ class HistoryPager extends ReverseChronologicalPager {
function submitButton( $message, $attributes = array() ) {
# Disable submit button if history has 1 revision only
if ( $this->getNumRows() > 1 ) {
- return Xml::submitButton( $message, $attributes );
+ return Html::submitButton( $message, $attributes );
} else {
return '';
}
@@ -610,11 +616,15 @@ class HistoryPager extends ReverseChronologicalPager {
$del = '';
$user = $this->getUser();
- // Show checkboxes for each revision
- if ( $user->isAllowed( 'deleterevision' ) ) {
+ $canRevDelete = $user->isAllowed( 'deleterevision' );
+ $showTagEditUI = ChangeTags::showTagEditingUI( $user );
+ // Show checkboxes for each revision, to allow for revision deletion and
+ // change tags
+ if ( $canRevDelete || $showTagEditUI ) {
$this->preventClickjacking();
- // If revision was hidden from sysops, disable the checkbox
- if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
+ // If revision was hidden from sysops and we don't need the checkbox
+ // for anything else, disable it
+ if ( !$showTagEditUI && !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
$del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
// Otherwise, enable the checkbox...
} else {
@@ -708,7 +718,7 @@ class HistoryPager extends ReverseChronologicalPager {
}
}
// Allow extension to add their own links here
- wfRunHooks( 'HistoryRevisionTools', array( $rev, &$tools ) );
+ Hooks::run( 'HistoryRevisionTools', array( $rev, &$tools ) );
if ( $tools ) {
$s2 .= ' ' . $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped();
@@ -726,7 +736,7 @@ class HistoryPager extends ReverseChronologicalPager {
$s .= ' <span class="mw-changeslist-separator">. .</span> ' . $s2;
}
- wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row, &$s, &$classes ) );
+ Hooks::run( 'PageHistoryLineEnding', array( $this, &$row, &$s, &$classes ) );
$attribs = array();
if ( $classes ) {
@@ -899,4 +909,5 @@ class HistoryPager extends ReverseChronologicalPager {
function getPreventClickjacking() {
return $this->preventClickjacking;
}
+
}
diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php
index f932a405..b5a73910 100644
--- a/includes/actions/InfoAction.php
+++ b/includes/actions/InfoAction.php
@@ -65,8 +65,8 @@ class InfoAction extends FormlessAction {
*/
public static function invalidateCache( Title $title ) {
global $wgMemc;
- // Clear page info.
- $revision = WikiPage::factory( $title )->getRevision();
+
+ $revision = Revision::newFromTitle( $title, 0, Revision::READ_LATEST );
if ( $revision !== null ) {
$key = wfMemcKey( 'infoaction', sha1( $title->getPrefixedText() ), $revision->getId() );
$wgMemc->delete( $key );
@@ -114,7 +114,7 @@ class InfoAction extends FormlessAction {
$pageInfo = $this->pageInfo();
// Allow extensions to add additional information
- wfRunHooks( 'InfoAction', array( $this->getContext(), &$pageInfo ) );
+ Hooks::run( 'InfoAction', array( $this->getContext(), &$pageInfo ) );
// Render page information
foreach ( $pageInfo as $header => $infoTable ) {
@@ -246,13 +246,13 @@ class InfoAction extends FormlessAction {
$pageInfo['header-basic'][] = array(
$this->msg( 'pageinfo-redirectsto' ),
Linker::link( $this->page->getRedirectTarget() ) .
- $this->msg( 'word-separator' )->text() .
- $this->msg( 'parentheses', Linker::link(
+ $this->msg( 'word-separator' )->escaped() .
+ $this->msg( 'parentheses' )->rawParams( Linker::link(
$this->page->getRedirectTarget(),
$this->msg( 'pageinfo-redirectsto-info' )->escaped(),
array(),
array( 'action' => 'info' )
- ) )->text()
+ ) )->escaped()
);
}
@@ -276,7 +276,9 @@ class InfoAction extends FormlessAction {
// Language in which the page content is (supposed to be) written
$pageLang = $title->getPageLanguage()->getCode();
- if ( $config->get( 'PageLanguageUseDB' ) && $this->getTitle()->userCan( 'pagelang' ) ) {
+ if ( $config->get( 'PageLanguageUseDB' )
+ && $this->getTitle()->userCan( 'pagelang', $this->getUser() )
+ ) {
// Link to Special:PageLanguage with pre-filled page title if user has permissions
$titleObj = SpecialPage::getTitleFor( 'PageLanguage', $title->getPrefixedText() );
$langDisp = Linker::link(
@@ -290,12 +292,12 @@ class InfoAction extends FormlessAction {
$pageInfo['header-basic'][] = array( $langDisp,
Language::fetchLanguageName( $pageLang, $lang->getCode() )
- . ' ' . $this->msg( 'parentheses', $pageLang ) );
+ . ' ' . $this->msg( 'parentheses', $pageLang )->escaped() );
// Content model of the page
$pageInfo['header-basic'][] = array(
$this->msg( 'pageinfo-content-model' ),
- ContentHandler::getLocalizedName( $title->getContentModel() )
+ htmlspecialchars( ContentHandler::getLocalizedName( $title->getContentModel() ) )
);
// Search engine status
@@ -314,13 +316,6 @@ class InfoAction extends FormlessAction {
$this->msg( 'pageinfo-robot-policy' ), $this->msg( "pageinfo-robot-${policy['index']}" )
);
- if ( isset( $pageCounts['views'] ) ) {
- // Number of views
- $pageInfo['header-basic'][] = array(
- $this->msg( 'pageinfo-views' ), $lang->formatNum( $pageCounts['views'] )
- );
- }
-
$unwatchedPageThreshold = $config->get( 'UnwatchedPageThreshold' );
if (
$user->isAllowed( 'unwatchedpages' ) ||
@@ -345,7 +340,11 @@ class InfoAction extends FormlessAction {
$whatLinksHere,
$this->msg( 'pageinfo-redirects-name' )->escaped(),
array(),
- array( 'hidelinks' => 1, 'hidetrans' => 1 )
+ array(
+ 'hidelinks' => 1,
+ 'hidetrans' => 1,
+ 'hideimages' => $title->getNamespace() == NS_FILE
+ )
),
$this->msg( 'pageinfo-redirects-value' )
->numParams( count( $title->getRedirectsHere() ) )
@@ -393,7 +392,7 @@ class InfoAction extends FormlessAction {
// Page protection
$pageInfo['header-restrictions'] = array();
- // Is this page effected by the cascading protection of something which includes it?
+ // Is this page affected by the cascading protection of something which includes it?
if ( $title->isCascadeProtected() ) {
$cascadingFrom = '';
$sources = $title->getCascadeProtectionSources(); // Array deferencing is in PHP 5.4 :(
@@ -484,7 +483,7 @@ class InfoAction extends FormlessAction {
$this->msg( 'pageinfo-firsttime' ),
Linker::linkKnown(
$title,
- $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
+ htmlspecialchars( $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ) ),
array(),
array( 'oldid' => $firstRev->getId() )
)
@@ -503,7 +502,7 @@ class InfoAction extends FormlessAction {
$this->msg( 'pageinfo-lasttime' ),
Linker::linkKnown(
$title,
- $lang->userTimeAndDate( $this->page->getTimestamp(), $user ),
+ htmlspecialchars( $lang->userTimeAndDate( $this->page->getTimestamp(), $user ) ),
array(),
array( 'oldid' => $this->page->getLatest() )
)
@@ -637,24 +636,12 @@ class InfoAction extends FormlessAction {
* @return array
*/
protected function pageCounts( Title $title ) {
- wfProfileIn( __METHOD__ );
$id = $title->getArticleID();
$config = $this->context->getConfig();
$dbr = wfGetDB( DB_SLAVE );
$result = array();
- if ( !$config->get( 'DisableCounters' ) ) {
- // Number of views
- $views = (int)$dbr->selectField(
- 'page',
- 'page_counter',
- array( 'page_id' => $id ),
- __METHOD__
- );
- $result['views'] = $views;
- }
-
// Number of page watchers
$watchers = (int)$dbr->selectField(
'watchlist',
@@ -761,8 +748,6 @@ class InfoAction extends FormlessAction {
__METHOD__
);
- wfProfileOut( __METHOD__ );
-
return $result;
}
diff --git a/includes/actions/RawAction.php b/includes/actions/RawAction.php
index d0d956ec..727bed20 100644
--- a/includes/actions/RawAction.php
+++ b/includes/actions/RawAction.php
@@ -117,7 +117,7 @@ class RawAction extends FormlessAction {
$response->header( 'HTTP/1.x 404 Not Found' );
}
- if ( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
+ if ( !Hooks::run( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
wfDebug( __METHOD__ . ": RawPageViewBeforeOutput hook broke raw page output.\n" );
}
diff --git a/includes/actions/RevertAction.php b/includes/actions/RevertAction.php
index 6481630e..d0258784 100644
--- a/includes/actions/RevertAction.php
+++ b/includes/actions/RevertAction.php
@@ -144,8 +144,6 @@ class RevertAction extends FormAction {
}
protected function getDescription() {
- $this->getOutput()->addBacklinkSubtitle( $this->getTitle() );
-
- return '';
+ return OutputPage::buildBacklinkSubtitle( $this->getTitle() );
}
}
diff --git a/includes/actions/RevisiondeleteAction.php b/includes/actions/RevisiondeleteAction.php
index b6eeb7b4..dbcb8485 100644
--- a/includes/actions/RevisiondeleteAction.php
+++ b/includes/actions/RevisiondeleteAction.php
@@ -27,8 +27,14 @@
* An action that just pass the request to Special:RevisionDelete
*
* @ingroup Actions
+ * @deprecated since 1.25 This class has been replaced by SpecialPageAction, but
+ * you really shouldn't have been using it outside core in the first place
*/
class RevisiondeleteAction extends FormlessAction {
+ public function __construct( Page $page, IContextSource $context = null ) {
+ wfDeprecated( 'RevisiondeleteAction class', '1.25' );
+ parent::__construct( $page, $context );
+ }
public function getName() {
return 'revisiondelete';
diff --git a/includes/actions/SpecialPageAction.php b/includes/actions/SpecialPageAction.php
new file mode 100644
index 00000000..9b721634
--- /dev/null
+++ b/includes/actions/SpecialPageAction.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
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+/**
+ * An action that just passes the request to the relevant special page
+ *
+ * @ingroup Actions
+ * @since 1.25
+ */
+class SpecialPageAction extends FormlessAction {
+
+ /**
+ * @var array A mapping of action names to special page names.
+ */
+ public static $actionToSpecialPageMapping = array(
+ 'revisiondelete' => 'Revisiondelete',
+ 'editchangetags' => 'EditTags',
+ );
+
+ public function getName() {
+ $request = $this->getRequest();
+ $actionName = $request->getVal( 'action', 'view' );
+ // TODO: Shouldn't need to copy-paste this code from Action::getActionName!
+ if ( $actionName === 'historysubmit' ) {
+ if ( $request->getBool( 'revisiondelete' ) ) {
+ $actionName = 'revisiondelete';
+ } elseif ( $request->getBool( 'editchangetags' ) ) {
+ $actionName = 'editchangetags';
+ }
+ }
+
+ if ( isset( self::$actionToSpecialPageMapping[$actionName] ) ) {
+ return $actionName;
+ }
+ return 'nosuchaction';
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+
+ public function getDescription() {
+ return '';
+ }
+
+ public function onView() {
+ return '';
+ }
+
+ public function show() {
+ $action = self::getName();
+ if ( $action === 'nosuchaction' ) {
+ throw new ErrorPageError( $this->msg( 'nosuchaction' ), $this->msg( 'nosuchactiontext' ) );
+ }
+
+ // map actions to (whitelisted) special pages
+ $special = SpecialPageFactory::getPage( self::$actionToSpecialPageMapping[$action] );
+ $special->setContext( $this->getContext() );
+ $special->getContext()->setTitle( $special->getPageTitle() );
+ $special->run( '' );
+ }
+}
diff --git a/includes/actions/UnwatchAction.php b/includes/actions/UnwatchAction.php
index e2e5a1d8..0a8628dd 100644
--- a/includes/actions/UnwatchAction.php
+++ b/includes/actions/UnwatchAction.php
@@ -36,9 +36,7 @@ class UnwatchAction extends WatchAction {
}
public function onSubmit( $data ) {
- wfProfileIn( __METHOD__ );
self::doUnwatch( $this->getTitle(), $this->getUser() );
- wfProfileOut( __METHOD__ );
return true;
}
diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php
index 8c9a46a5..96473409 100644
--- a/includes/actions/WatchAction.php
+++ b/includes/actions/WatchAction.php
@@ -48,9 +48,7 @@ class WatchAction extends FormAction {
}
public function onSubmit( $data ) {
- wfProfileIn( __METHOD__ );
self::doWatch( $this->getTitle(), $this->getUser() );
- wfProfileOut( __METHOD__ );
return true;
}
@@ -132,10 +130,10 @@ class WatchAction extends FormAction {
$page = WikiPage::factory( $title );
$status = Status::newFatal( 'hookaborted' );
- if ( wfRunHooks( 'WatchArticle', array( &$user, &$page, &$status ) ) ) {
+ if ( Hooks::run( 'WatchArticle', array( &$user, &$page, &$status ) ) ) {
$status = Status::newGood();
$user->addWatch( $title, $checkRights );
- wfRunHooks( 'WatchArticleComplete', array( &$user, &$page ) );
+ Hooks::run( 'WatchArticleComplete', array( &$user, &$page ) );
}
return $status;
@@ -156,10 +154,10 @@ class WatchAction extends FormAction {
$page = WikiPage::factory( $title );
$status = Status::newFatal( 'hookaborted' );
- if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$page, &$status ) ) ) {
+ if ( Hooks::run( 'UnwatchArticle', array( &$user, &$page, &$status ) ) ) {
$status = Status::newGood();
$user->removeWatch( $title );
- wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$page ) );
+ Hooks::run( 'UnwatchArticleComplete', array( &$user, &$page ) );
}
return $status;
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 944e4895..5a1eb995 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -32,9 +32,6 @@
* Module parameters: Derived classes can define getAllowedParams() to specify
* which parameters to expect, how to parse and validate them.
*
- * Profiling: various methods to allow keeping tabs on various tasks and their
- * time costs
- *
* Self-documentation: code to allow the API to document its own state
*
* @ingroup API
@@ -64,6 +61,30 @@ abstract class ApiBase extends ContextSource {
// Boolean, if MIN/MAX are set, enforce (die) these?
// Only applies if TYPE='integer' Use with extreme caution
const PARAM_RANGE_ENFORCE = 9;
+ /// @since 1.25
+ // Specify an alternative i18n message for this help parameter.
+ // Value is $msg for ApiBase::makeMessage()
+ const PARAM_HELP_MSG = 10;
+ /// @since 1.25
+ // Specify additional i18n messages to append to the normal message. Value
+ // is an array of $msg for ApiBase::makeMessage()
+ const PARAM_HELP_MSG_APPEND = 11;
+ /// @since 1.25
+ // Specify additional information tags for the parameter. Value is an array
+ // of arrays, with the first member being the 'tag' for the info and the
+ // remaining members being the values. In the help, this is formatted using
+ // apihelp-{$path}-paraminfo-{$tag}, which is passed $1 = count, $2 =
+ // comma-joined list of values, $3 = module prefix.
+ const PARAM_HELP_MSG_INFO = 12;
+ /// @since 1.25
+ // When PARAM_TYPE is an array, this may be an array mapping those values
+ // to page titles which will be linked in the help.
+ const PARAM_VALUE_LINKS = 13;
+ /// @since 1.25
+ // When PARAM_TYPE is an array, this is an array mapping those values to
+ // $msg for ApiBase::makeMessage(). Any value not having a mapping will use
+ // apihelp-{$path}-paramvalue-{$param}-{$value} is used.
+ const PARAM_HELP_MSG_PER_VALUE = 14;
const LIMIT_BIG1 = 500; // Fast query, std user limit
const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
@@ -136,6 +157,9 @@ abstract class ApiBase extends ContextSource {
* 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.
+ * @note Do not use this just because you don't want to support non-json
+ * formats. This should be used only when there is a fundamental
+ * requirement for a specific format.
* @return mixed Instance of a derived class of ApiFormatBase, or null
*/
public function getCustomPrinter() {
@@ -143,27 +167,64 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Returns the description string for this module
- * @return string|array
+ * Returns usage examples for this module.
+ *
+ * Return value has query strings as keys, with values being either strings
+ * (message key), arrays (message key + parameter), or Message objects.
+ *
+ * Do not call this base class implementation when overriding this method.
+ *
+ * @since 1.25
+ * @return array
*/
- protected function getDescription() {
- return false;
- }
+ protected function getExamplesMessages() {
+ // Fall back to old non-localised method
+ $ret = array();
+
+ $examples = $this->getExamples();
+ if ( $examples ) {
+ if ( !is_array( $examples ) ) {
+ $examples = array( $examples );
+ } elseif ( $examples && ( count( $examples ) & 1 ) == 0 &&
+ array_keys( $examples ) === range( 0, count( $examples ) - 1 ) &&
+ !preg_match( '/^\s*api\.php\?/', $examples[0] )
+ ) {
+ // Fix up the ugly "even numbered elements are description, odd
+ // numbered elemts are the link" format (see doc for self::getExamples)
+ $tmp = array();
+ for ( $i = 0; $i < count( $examples ); $i += 2 ) {
+ $tmp[$examples[$i + 1]] = $examples[$i];
+ }
+ $examples = $tmp;
+ }
- /**
- * Returns usage examples for this module. Return false if no examples are available.
- * @return bool|string|array
- */
- protected function getExamples() {
- return false;
+ foreach ( $examples as $k => $v ) {
+ if ( is_numeric( $k ) ) {
+ $qs = $v;
+ $msg = '';
+ } else {
+ $qs = $k;
+ $msg = self::escapeWikiText( $v );
+ if ( is_array( $msg ) ) {
+ $msg = join( " ", $msg );
+ }
+ }
+
+ $qs = preg_replace( '/^\s*api\.php\?/', '', $qs );
+ $ret[$qs] = $this->msg( 'api-help-fallback-example', array( $msg ) );
+ }
+ }
+
+ return $ret;
}
/**
- * @return bool|string|array Returns a false if the module has no help URL,
- * else returns a (array of) string
+ * Return links to more detailed help pages about the module.
+ * @since 1.25, returning boolean false is deprecated
+ * @return string|array
*/
public function getHelpUrls() {
- return false;
+ return array();
}
/**
@@ -176,22 +237,12 @@ abstract class ApiBase extends ContextSource {
* in the overriding methods. Callers of this method can pass zero or
* more OR-ed flags like GET_VALUES_FOR_HELP.
*
- * @return array|bool
+ * @return array
*/
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;
- }
-
- /**
- * 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
- */
- protected function getParamDescription() {
- return false;
+ return array();
}
/**
@@ -227,6 +278,25 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Indicates whether this module is deprecated
+ * @since 1.25
+ * @return bool
+ */
+ public function isDeprecated() {
+ return false;
+ }
+
+ /**
+ * Indicates whether this module is "internal"
+ * Internal API modules are not (yet) intended for 3rd party use and may be unstable.
+ * @since 1.25
+ * @return bool
+ */
+ public function isInternal() {
+ return false;
+ }
+
+ /**
* Returns the token type this module requires in order to execute.
*
* Modules are strongly encouraged to use the core 'csrf' type unless they
@@ -234,11 +304,9 @@ abstract class ApiBase extends ContextSource {
* 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.
+ * Returning a non-falsey value here will force the addition of an
+ * appropriate 'token' parameter in self::getFinalParams(). 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
@@ -304,6 +372,87 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * Get the parent of this module
+ * @since 1.25
+ * @return ApiBase|null
+ */
+ public function getParent() {
+ return $this->isMain() ? null : $this->getMain();
+ }
+
+ /**
+ * Returns true if the current request breaks the same-origin policy.
+ *
+ * For example, json with callbacks.
+ *
+ * https://en.wikipedia.org/wiki/Same-origin_policy
+ *
+ * @since 1.25
+ * @return bool
+ */
+ public function lacksSameOriginSecurity() {
+ return $this->getMain()->getRequest()->getVal( 'callback' ) !== null;
+ }
+
+ /**
+ * Get the path to this module
+ *
+ * @since 1.25
+ * @return string
+ */
+ public function getModulePath() {
+ if ( $this->isMain() ) {
+ return 'main';
+ } elseif ( $this->getParent()->isMain() ) {
+ return $this->getModuleName();
+ } else {
+ return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
+ }
+ }
+
+ /**
+ * Get a module from its module path
+ *
+ * @since 1.25
+ * @param string $path
+ * @return ApiBase|null
+ * @throws UsageException
+ */
+ public function getModuleFromPath( $path ) {
+ $module = $this->getMain();
+ if ( $path === 'main' ) {
+ return $module;
+ }
+
+ $parts = explode( '+', $path );
+ if ( count( $parts ) === 1 ) {
+ // In case the '+' was typed into URL, it resolves as a space
+ $parts = explode( ' ', $path );
+ }
+
+ $count = count( $parts );
+ for ( $i = 0; $i < $count; $i++ ) {
+ $parent = $module;
+ $manager = $parent->getModuleManager();
+ if ( $manager === null ) {
+ $errorPath = join( '+', array_slice( $parts, 0, $i ) );
+ $this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' );
+ }
+ $module = $manager->getModule( $parts[$i] );
+
+ if ( $module === null ) {
+ $errorPath = $i ? join( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
+ $this->dieUsage(
+ "The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"",
+ 'badmodule'
+ );
+ }
+ }
+
+ return $module;
+ }
+
+ /**
* Get the result object
* @return ApiResult
*/
@@ -318,11 +467,17 @@ abstract class ApiBase extends ContextSource {
}
/**
- * Get the result data array (read-only)
- * @return array
+ * Get the error formatter
+ * @return ApiErrorFormatter
*/
- public function getResultData() {
- return $this->getResult()->getData();
+ public function getErrorFormatter() {
+ // Main module has getErrorFormatter() method overridden
+ // Safety - avoid infinite loop:
+ if ( $this->isMain() ) {
+ ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
+ }
+
+ return $this->getMain()->getErrorFormatter();
}
/**
@@ -331,76 +486,38 @@ abstract class ApiBase extends ContextSource {
*/
protected function getDB() {
if ( !isset( $this->mSlaveDB ) ) {
- $this->profileDBIn();
$this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
- $this->profileDBOut();
}
return $this->mSlaveDB;
}
/**
- * 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 ) );
-
- return $desc;
- }
-
- /**
- * Get final list of parameters, after hooks have had a chance to
- * tweak it as needed.
- *
- * @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
- * @return array|bool False on no parameters
- * @since 1.21 $flags param added
+ * Get the continuation manager
+ * @return ApiContinuationManager|null
*/
- public function getFinalParams( $flags = 0 ) {
- $params = $this->getAllowedParams( $flags );
-
- if ( $this->needsToken() ) {
- $params['token'] = array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true,
- );
+ public function getContinuationManager() {
+ // Main module has getContinuationManager() method overridden
+ // Safety - avoid infinite loop:
+ if ( $this->isMain() ) {
+ ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
}
- wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
-
- return $params;
+ return $this->getMain()->getContinuationManager();
}
/**
- * Get final parameter descriptions, after hooks have had a chance to tweak it as
- * needed.
- *
- * @return array|bool False on no parameter descriptions
+ * Set the continuation manager
+ * @param ApiContinuationManager|null
*/
- public function getFinalParamDescription() {
- $desc = $this->getParamDescription();
-
- $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"
- );
+ public function setContinuationManager( $manager ) {
+ // Main module has setContinuationManager() method overridden
+ // Safety - avoid infinite loop:
+ if ( $this->isMain() ) {
+ ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
}
- wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
-
- return $desc;
+ $this->getMain()->setContinuationManager( $manager );
}
/**@}*/
@@ -782,7 +899,7 @@ abstract class ApiBase extends ContextSource {
$value = $this->getMain()->canApiHighLimits()
? $paramSettings[self::PARAM_MAX2]
: $paramSettings[self::PARAM_MAX];
- $this->getResult()->setParsedLimit( $this->getModuleName(), $value );
+ $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
} else {
$value = intval( $value );
$this->validateLimit(
@@ -974,8 +1091,9 @@ abstract class ApiBase extends ContextSource {
* @param string $token Supplied token
* @param array $params All supplied parameters for the module
* @return bool
+ * @throws MWException
*/
- public final function validateToken( $token, array $params ) {
+ final public function validateToken( $token, array $params ) {
$tokenType = $this->needsToken();
$salts = ApiQueryTokens::getTokenTypeSalts();
if ( !isset( $salts[$tokenType] ) ) {
@@ -1093,6 +1211,55 @@ abstract class ApiBase extends ContextSource {
return $user;
}
+ /**
+ * A subset of wfEscapeWikiText for BC texts
+ *
+ * @since 1.25
+ * @param string|array $v
+ * @return string|array
+ */
+ private static function escapeWikiText( $v ) {
+ if ( is_array( $v ) ) {
+ return array_map( 'self::escapeWikiText', $v );
+ } else {
+ return strtr( $v, array(
+ '__' => '_&#95;', '{' => '&#123;', '}' => '&#125;',
+ '[[Category:' => '[[:Category:',
+ '[[File:' => '[[:File:', '[[Image:' => '[[:Image:',
+ ) );
+ }
+ }
+
+ /**
+ * Create a Message from a string or array
+ *
+ * A string is used as a message key. An array has the message key as the
+ * first value and message parameters as subsequent values.
+ *
+ * @since 1.25
+ * @param string|array|Message $msg
+ * @param IContextSource $context
+ * @param array $params
+ * @return Message|null
+ */
+ public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
+ if ( is_string( $msg ) ) {
+ $msg = wfMessage( $msg );
+ } elseif ( is_array( $msg ) ) {
+ $msg = call_user_func_array( 'wfMessage', $msg );
+ }
+ if ( !$msg instanceof Message ) {
+ return null;
+ }
+
+ $msg->setContext( $context );
+ if ( $params ) {
+ $msg->params( $params );
+ }
+
+ return $msg;
+ }
+
/**@}*/
/************************************************************************//**
@@ -1108,28 +1275,8 @@ abstract class ApiBase extends ContextSource {
* @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 );
+ $msg = new ApiRawMessage( $warning, 'warning' );
+ $this->getErrorFormatter()->addWarning( $this->getModuleName(), $msg );
}
/**
@@ -1159,7 +1306,6 @@ abstract class ApiBase extends ContextSource {
* @throws UsageException
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
- Profiler::instance()->close();
throw new UsageException(
$description,
$this->encodeParamName( $errorCode ),
@@ -1174,6 +1320,7 @@ abstract class ApiBase extends ContextSource {
* @since 1.23
* @param Status $status
* @return array Array of code and error string
+ * @throws MWException
*/
public function getErrorFromStatus( $status ) {
if ( $status->isGood() ) {
@@ -1530,6 +1677,10 @@ abstract class ApiBase extends ContextSource {
'code' => 'nosuchrcid',
'info' => "There is no change with rcid \"\$1\""
),
+ 'nosuchlogid' => array(
+ 'code' => 'nosuchlogid',
+ 'info' => "There is no log entry with ID \"\$1\""
+ ),
'protect-invalidaction' => array(
'code' => 'protect-invalidaction',
'info' => "Invalid protection type \"\$1\""
@@ -1815,6 +1966,21 @@ abstract class ApiBase extends ContextSource {
throw new MWException( "Internal error in $method: $message" );
}
+ /**
+ * Write logging information for API features to a debug log, for usage
+ * analysis.
+ * @param string $feature Feature being used.
+ */
+ protected function logFeatureUsage( $feature ) {
+ $request = $this->getRequest();
+ $s = '"' . addslashes( $feature ) . '"' .
+ ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
+ ' "' . $request->getIP() . '"' .
+ ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
+ ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
+ wfDebugLog( 'api-feature-usage', $s, 'private' );
+ }
+
/**@}*/
/************************************************************************//**
@@ -1823,10 +1989,424 @@ abstract class ApiBase extends ContextSource {
*/
/**
+ * Return the description message.
+ *
+ * @return string|array|Message
+ */
+ protected function getDescriptionMessage() {
+ return "apihelp-{$this->getModulePath()}-description";
+ }
+
+ /**
+ * Get final module description, after hooks have had a chance to tweak it as
+ * needed.
+ *
+ * @since 1.25, returns Message[] rather than string[]
+ * @return Message[]
+ */
+ public function getFinalDescription() {
+ $desc = $this->getDescription();
+ Hooks::run( 'APIGetDescription', array( &$this, &$desc ) );
+ $desc = self::escapeWikiText( $desc );
+ if ( is_array( $desc ) ) {
+ $desc = join( "\n", $desc );
+ } else {
+ $desc = (string)$desc;
+ }
+
+ $msg = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), array(
+ $this->getModulePrefix(),
+ $this->getModuleName(),
+ $this->getModulePath(),
+ ) );
+ if ( !$msg->exists() ) {
+ $msg = $this->msg( 'api-help-fallback-description', $desc );
+ }
+ $msgs = array( $msg );
+
+ Hooks::run( 'APIGetDescriptionMessages', array( $this, &$msgs ) );
+
+ return $msgs;
+ }
+
+ /**
+ * Get final list of parameters, after hooks have had a chance to
+ * tweak it as needed.
+ *
+ * @param int $flags Zero or more flags like GET_VALUES_FOR_HELP
+ * @return array|bool False on no parameters
+ * @since 1.21 $flags param added
+ */
+ public function getFinalParams( $flags = 0 ) {
+ $params = $this->getAllowedParams( $flags );
+ if ( !$params ) {
+ $params = array();
+ }
+
+ if ( $this->needsToken() ) {
+ $params['token'] = array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ApiBase::PARAM_HELP_MSG => array(
+ 'api-help-param-token',
+ $this->needsToken(),
+ ),
+ ) + ( isset( $params['token'] ) ? $params['token'] : array() );
+ }
+
+ Hooks::run( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
+
+ return $params;
+ }
+
+ /**
+ * Get final parameter descriptions, after hooks have had a chance to tweak it as
+ * needed.
+ *
+ * @since 1.25, returns array of Message[] rather than array of string[]
+ * @return array Keys are parameter names, values are arrays of Message objects
+ */
+ public function getFinalParamDescription() {
+ $prefix = $this->getModulePrefix();
+ $name = $this->getModuleName();
+ $path = $this->getModulePath();
+
+ $desc = $this->getParamDescription();
+ Hooks::run( 'APIGetParamDescription', array( &$this, &$desc ) );
+
+ if ( !$desc ) {
+ $desc = array();
+ }
+ $desc = self::escapeWikiText( $desc );
+
+ $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+ $msgs = array();
+ foreach ( $params as $param => $settings ) {
+ if ( !is_array( $settings ) ) {
+ $settings = array();
+ }
+
+ $d = isset( $desc[$param] ) ? $desc[$param] : '';
+ if ( is_array( $d ) ) {
+ // Special handling for prop parameters
+ $d = array_map( function ( $line ) {
+ if ( preg_match( '/^\s+(\S+)\s+-\s+(.+)$/', $line, $m ) ) {
+ $line = "\n;{$m[1]}:{$m[2]}";
+ }
+ return $line;
+ }, $d );
+ $d = join( ' ', $d );
+ }
+
+ if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
+ $msg = $settings[ApiBase::PARAM_HELP_MSG];
+ } else {
+ $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
+ if ( !$msg->exists() ) {
+ $msg = $this->msg( 'api-help-fallback-parameter', $d );
+ }
+ }
+ $msg = ApiBase::makeMessage( $msg, $this->getContext(),
+ array( $prefix, $param, $name, $path ) );
+ if ( !$msg ) {
+ $this->dieDebug( __METHOD__,
+ 'Value in ApiBase::PARAM_HELP_MSG is not valid' );
+ }
+ $msgs[$param] = array( $msg );
+
+ if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
+ if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
+ $this->dieDebug( __METHOD__,
+ 'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
+ }
+ if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) {
+ $this->dieDebug( __METHOD__,
+ 'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
+ 'ApiBase::PARAM_TYPE is an array' );
+ }
+
+ $valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE];
+ foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) {
+ if ( isset( $valueMsgs[$value] ) ) {
+ $msg = $valueMsgs[$value];
+ } else {
+ $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
+ }
+ $m = ApiBase::makeMessage( $msg, $this->getContext(),
+ array( $prefix, $param, $name, $path, $value ) );
+ if ( $m ) {
+ $m = new ApiHelpParamValueMessage(
+ $value,
+ array( $m->getKey(), 'api-help-param-no-description' ),
+ $m->getParams()
+ );
+ $msgs[$param][] = $m->setContext( $this->getContext() );
+ } else {
+ $this->dieDebug( __METHOD__,
+ "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
+ }
+ }
+ }
+
+ if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
+ if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
+ $this->dieDebug( __METHOD__,
+ 'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
+ }
+ foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
+ $m = ApiBase::makeMessage( $m, $this->getContext(),
+ array( $prefix, $param, $name, $path ) );
+ if ( $m ) {
+ $msgs[$param][] = $m;
+ } else {
+ $this->dieDebug( __METHOD__,
+ 'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
+ }
+ }
+ }
+ }
+
+ Hooks::run( 'APIGetParamDescriptionMessages', array( $this, &$msgs ) );
+
+ return $msgs;
+ }
+
+ /**
+ * Generates the list of flags for the help screen and for action=paraminfo
+ *
+ * Corresponding messages: api-help-flag-deprecated,
+ * api-help-flag-internal, api-help-flag-readrights,
+ * api-help-flag-writerights, api-help-flag-mustbeposted
+ *
+ * @return string[]
+ */
+ protected function getHelpFlags() {
+ $flags = array();
+
+ if ( $this->isDeprecated() ) {
+ $flags[] = 'deprecated';
+ }
+ if ( $this->isInternal() ) {
+ $flags[] = 'internal';
+ }
+ if ( $this->isReadMode() ) {
+ $flags[] = 'readrights';
+ }
+ if ( $this->isWriteMode() ) {
+ $flags[] = 'writerights';
+ }
+ if ( $this->mustBePosted() ) {
+ $flags[] = 'mustbeposted';
+ }
+
+ return $flags;
+ }
+
+ /**
+ * Called from ApiHelp before the pieces are joined together and returned.
+ *
+ * This exists mainly for ApiMain to add the Permissions and Credits
+ * sections. Other modules probably don't need it.
+ *
+ * @param string[] &$help Array of help data
+ * @param array $options Options passed to ApiHelp::getHelp
+ */
+ public function modifyHelp( array &$help, array $options ) {
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @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;
+
+ /**
+ * 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 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();
+ }
+
+ /**
+ * Returns the description string for this module
+ *
+ * Ignored if an i18n message exists for
+ * "apihelp-{$this->getModulePathString()}-description".
+ *
+ * @deprecated since 1.25
+ * @return Message|string|array
+ */
+ protected function getDescription() {
+ return false;
+ }
+
+ /**
+ * Returns an array of parameter descriptions.
+ *
+ * For each parameter, ignored if an i18n message exists for the parameter.
+ * By default that message is
+ * "apihelp-{$this->getModulePathString()}-param-{$param}", but it may be
+ * overridden using ApiBase::PARAM_HELP_MSG in the data returned by
+ * self::getFinalParams().
+ *
+ * @deprecated since 1.25
+ * @return array|bool False on no parameter descriptions
+ */
+ protected function getParamDescription() {
+ return array();
+ }
+
+ /**
+ * Returns usage examples for this module.
+ *
+ * Return value as an array is either:
+ * - numeric keys with partial URLs ("api.php?" plus a query string) as
+ * values
+ * - sequential numeric keys with even-numbered keys being display-text
+ * and odd-numbered keys being partial urls
+ * - partial URLs as keys with display-text (string or array-to-be-joined)
+ * as values
+ * Return value as a string is the same as an array with a numeric key and
+ * that value, and boolean false means "no examples".
+ *
+ * @deprecated since 1.25, use getExamplesMessages() instead
+ * @return bool|string|array
+ */
+ protected function getExamples() {
+ return false;
+ }
+
+ /**
* Generates help message for this module, or false if there is no description
+ * @deprecated since 1.25
* @return string|bool
*/
public function makeHelpMsg() {
+ wfDeprecated( __METHOD__, '1.25' );
static $lnPrfx = "\n ";
$msg = $this->getFinalDescription();
@@ -1891,6 +2471,7 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * @deprecated since 1.25
* @param string $item
* @return string
*/
@@ -1899,12 +2480,14 @@ abstract class ApiBase extends ContextSource {
}
/**
+ * @deprecated since 1.25
* @param string $prefix Text to split output items
* @param string $title What is being output
* @param string|array $input
* @return string
*/
protected function makeHelpArrayToString( $prefix, $title, $input ) {
+ wfDeprecated( __METHOD__, '1.25' );
if ( $input === false ) {
return '';
}
@@ -1929,9 +2512,11 @@ abstract class ApiBase extends ContextSource {
/**
* Generates the parameter descriptions for this module, to be displayed in the
* module's help.
+ * @deprecated since 1.25
* @return string|bool
*/
public function makeHelpMsgParameters() {
+ wfDeprecated( __METHOD__, '1.25' );
$params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
if ( $params ) {
@@ -2081,292 +2666,79 @@ abstract class ApiBase extends ContextSource {
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
- *
+ * @deprecated since 1.25, always returns empty string
* @param DatabaseBase|bool $db
- *
* @return string
*/
public function getModuleProfileName( $db = false ) {
- if ( $db ) {
- return 'API:' . $this->mModuleName . '-DB';
- }
-
- return 'API:' . $this->mModuleName;
+ wfDeprecated( __METHOD__, '1.25' );
+ return '';
}
/**
- * Start module profiling
+ * @deprecated since 1.25
*/
public function profileIn() {
- if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileOut()' );
- }
- $this->mTimeIn = microtime( true );
- wfProfileIn( $this->getModuleProfileName() );
+ // No wfDeprecated() yet because extensions call this and might need to
+ // keep doing so for BC.
}
/**
- * End module profiling
+ * @deprecated since 1.25
*/
public function profileOut() {
- if ( $this->mTimeIn === 0 ) {
- 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()'
- );
- }
-
- $this->mModuleTime += microtime( true ) - $this->mTimeIn;
- $this->mTimeIn = 0;
- wfProfileOut( $this->getModuleProfileName() );
+ // No wfDeprecated() yet because extensions call this and might need to
+ // keep doing so for BC.
}
/**
- * When modules crash, sometimes it is needed to do a profileOut() regardless
- * of the profiling state the module was in. This method does such cleanup.
+ * @deprecated since 1.25
*/
public function safeProfileOut() {
- if ( $this->mTimeIn !== 0 ) {
- if ( $this->mDBTimeIn !== 0 ) {
- $this->profileDBOut();
- }
- $this->profileOut();
- }
+ wfDeprecated( __METHOD__, '1.25' );
}
/**
- * Total time the module was executed
+ * @deprecated since 1.25, always returns 0
* @return float
*/
public function getProfileTime() {
- if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called without calling profileOut() first' );
- }
-
- return $this->mModuleTime;
+ wfDeprecated( __METHOD__, '1.25' );
+ return 0;
}
/**
- * Profiling: database execution time
- */
- private $mDBTimeIn = 0, $mDBTime = 0;
-
- /**
- * Start module profiling
+ * @deprecated since 1.25
*/
public function profileDBIn() {
- if ( $this->mTimeIn === 0 ) {
- 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()' );
- }
- $this->mDBTimeIn = microtime( true );
- wfProfileIn( $this->getModuleProfileName( true ) );
+ wfDeprecated( __METHOD__, '1.25' );
}
/**
- * End database profiling
+ * @deprecated since 1.25
*/
public function profileDBOut() {
- if ( $this->mTimeIn === 0 ) {
- 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' );
- }
-
- $time = microtime( true ) - $this->mDBTimeIn;
- $this->mDBTimeIn = 0;
-
- $this->mDBTime += $time;
- $this->getMain()->mDBTime += $time;
- wfProfileOut( $this->getModuleProfileName( true ) );
+ wfDeprecated( __METHOD__, '1.25' );
}
/**
- * Total time the module used the database
+ * @deprecated since 1.25, always returns 0
* @return float
*/
public function getProfileDBTime() {
- if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBOut() first' );
- }
-
- return $this->mDBTime;
- }
-
- /**
- * Write logging information for API features to a debug log, for usage
- * analysis.
- * @param string $feature Feature being used.
- */
- 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;
-
- /**
- * 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 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();
+ wfDeprecated( __METHOD__, '1.25' );
+ return 0;
}
/**
- * @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
+ * Get the result data array (read-only)
+ * @deprecated since 1.25, use $this->getResult() methods instead
* @return array
*/
- public function parseErrors( $errors ) {
- wfDeprecated( __METHOD__, '1.24' );
- return array();
+ public function getResultData() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return $this->getResult()->getData();
}
/**@}*/
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index 07f62c66..4d39ce1b 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -78,7 +78,7 @@ class ApiBlock extends ApiBase {
'other',
$params['reason']
),
- 'Expiry' => $params['expiry'] == 'never' ? 'infinite' : $params['expiry'],
+ 'Expiry' => $params['expiry'],
'HardBlock' => !$params['anononly'],
'CreateAccount' => $params['nocreate'],
'AutoBlock' => $params['autoblock'],
@@ -113,27 +113,13 @@ class ApiBlock extends ApiBase {
}
$res['reason'] = $params['reason'];
- if ( $params['anononly'] ) {
- $res['anononly'] = '';
- }
- if ( $params['nocreate'] ) {
- $res['nocreate'] = '';
- }
- if ( $params['autoblock'] ) {
- $res['autoblock'] = '';
- }
- if ( $params['noemail'] ) {
- $res['noemail'] = '';
- }
- if ( $params['hidename'] ) {
- $res['hidename'] = '';
- }
- if ( $params['allowusertalk'] ) {
- $res['allowusertalk'] = '';
- }
- if ( $params['watchuser'] ) {
- $res['watchuser'] = '';
- }
+ $res['anononly'] = $params['anononly'];
+ $res['nocreate'] = $params['nocreate'];
+ $res['autoblock'] = $params['autoblock'];
+ $res['noemail'] = $params['noemail'];
+ $res['hidename'] = $params['hidename'];
+ $res['allowusertalk'] = $params['allowusertalk'];
+ $res['watchuser'] = $params['watchuser'];
$this->getResult()->addValue( null, $this->getModuleName(), $res );
}
@@ -165,38 +151,16 @@ class ApiBlock extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'user' => 'Username, IP address or IP range you want to block',
- '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.)',
- '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)',
- 'reblock' => 'If the user is already blocked, overwrite the existing block',
- 'watchuser' => 'Watch the user/IP\'s user and talk pages',
- );
- }
-
- public function getDescription() {
- return 'Block a user.';
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- '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'
+ 'action=block&user=192.0.2.5&expiry=3%20days&reason=First%20strike&token=123ABC'
+ => 'apihelp-block-example-ip-simple',
+ 'action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
+ => 'apihelp-block-example-user-complex',
);
}
diff --git a/includes/api/ApiCheckToken.php b/includes/api/ApiCheckToken.php
new file mode 100644
index 00000000..28c6ece7
--- /dev/null
+++ b/includes/api/ApiCheckToken.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Created on Jan 29, 2015
+ *
+ * Copyright © 2015 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.25
+ * @ingroup API
+ */
+class ApiCheckToken extends ApiBase {
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $token = $params['token'];
+ $maxage = $params['maxtokenage'];
+ $request = $this->getRequest();
+ $salts = ApiQueryTokens::getTokenTypeSalts();
+ $salt = $salts[$params['type']];
+
+ $res = array();
+
+ if ( $this->getUser()->matchEditToken( $token, $salt, $request, $maxage ) ) {
+ $res['result'] = 'valid';
+ } elseif ( $maxage !== null && $this->getUser()->matchEditToken( $token, $salt, $request ) ) {
+ $res['result'] = 'expired';
+ } else {
+ $res['result'] = 'invalid';
+ }
+
+ $ts = User::getEditTokenTimestamp( $token );
+ if ( $ts !== null ) {
+ $mwts = new MWTimestamp();
+ $mwts->timestamp->setTimestamp( $ts );
+ $res['generated'] = $mwts->getTimestamp( TS_ISO_8601 );
+ }
+
+ $this->getResult()->addValue( null, $this->getModuleName(), $res );
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'type' => array(
+ ApiBase::PARAM_TYPE => array_keys( ApiQueryTokens::getTokenTypeSalts() ),
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'token' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'maxtokenage' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
+ );
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=checktoken&type=csrf&token=123ABC'
+ => 'apihelp-checktoken-example-simple',
+ );
+ }
+}
diff --git a/includes/api/ApiClearHasMsg.php b/includes/api/ApiClearHasMsg.php
index 32e20e80..eb471ae6 100644
--- a/includes/api/ApiClearHasMsg.php
+++ b/includes/api/ApiClearHasMsg.php
@@ -42,13 +42,10 @@ class ApiClearHasMsg extends ApiBase {
return false;
}
- public function getDescription() {
- return array( 'Clears the hasmsg flag for current user.' );
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=clearhasmsg' => 'Clears the hasmsg flag for current user',
+ 'action=clearhasmsg'
+ => 'apihelp-clearhasmsg-example-1',
);
}
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
index 48559268..23009123 100644
--- a/includes/api/ApiComparePages.php
+++ b/includes/api/ApiComparePages.php
@@ -72,7 +72,7 @@ class ApiComparePages extends ApiBase {
);
}
- ApiResult::setContent( $vals, $difftext );
+ ApiResult::setContentValue( $vals, 'body', $difftext );
$this->getResult()->addValue( null, $this->getModuleName(), $vals );
}
@@ -126,27 +126,10 @@ class ApiComparePages extends ApiBase {
);
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
return array(
- 'fromtitle' => 'First title to compare',
- 'fromid' => 'First page ID to compare',
- 'fromrev' => 'First revision to compare',
- 'totitle' => 'Second title to compare',
- 'toid' => 'Second page ID to compare',
- 'torev' => 'Second revision to compare',
- );
- }
-
- 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).'
- );
- }
-
- public function getExamples() {
- return array(
- 'api.php?action=compare&fromrev=1&torev=2' => 'Create a diff between revision 1 and 2',
+ 'action=compare&fromrev=1&torev=2'
+ => 'apihelp-compare-example-1',
);
}
}
diff --git a/includes/api/ApiContinuationManager.php b/includes/api/ApiContinuationManager.php
new file mode 100644
index 00000000..354f4e7d
--- /dev/null
+++ b/includes/api/ApiContinuationManager.php
@@ -0,0 +1,238 @@
+<?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
+ */
+
+/**
+ * This manages continuation state.
+ * @since 1.25 this is no longer a subclass of ApiBase
+ * @ingroup API
+ */
+class ApiContinuationManager {
+ private $source;
+
+ private $allModules = array();
+ private $generatedModules = array();
+
+ private $continuationData = array();
+ private $generatorContinuationData = array();
+
+ private $generatorParams = array();
+ private $generatorDone = false;
+
+ /**
+ * @param ApiBase $module Module starting the continuation
+ * @param ApiBase[] $allModules Contains ApiBase instances that will be executed
+ * @param array $generatedModules Names of modules that depend on the generator
+ */
+ public function __construct(
+ ApiBase $module, array $allModules = array(), array $generatedModules = array()
+ ) {
+ $this->source = get_class( $module );
+ $request = $module->getRequest();
+
+ $this->generatedModules = $generatedModules
+ ? array_combine( $generatedModules, $generatedModules )
+ : array();
+
+ $skip = array();
+ $continue = $request->getVal( 'continue', '' );
+ if ( $continue !== '' ) {
+ $continue = explode( '||', $continue );
+ if ( count( $continue ) !== 2 ) {
+ throw new UsageException(
+ 'Invalid continue param. You should pass the original value returned by the previous query',
+ 'badcontinue'
+ );
+ }
+ $this->generatorDone = ( $continue[0] === '-' );
+ $skip = explode( '|', $continue[1] );
+ if ( !$this->generatorDone ) {
+ $params = explode( '|', $continue[0] );
+ if ( $params ) {
+ $this->generatorParams = array_intersect_key(
+ $request->getValues(),
+ array_flip( $params )
+ );
+ }
+ } else {
+ // When the generator is complete, don't run any modules that
+ // depend on it.
+ $skip += $this->generatedModules;
+ }
+ }
+
+ foreach ( $allModules as $module ) {
+ $name = $module->getModuleName();
+ if ( in_array( $name, $skip, true ) ) {
+ $this->allModules[$name] = false;
+ // Prevent spurious "unused parameter" warnings
+ $module->extractRequestParams();
+ } else {
+ $this->allModules[$name] = $module;
+ }
+ }
+ }
+
+ /**
+ * Get the class that created this manager
+ * @return string
+ */
+ public function getSource() {
+ return $this->source;
+ }
+
+ /**
+ * Is the generator done?
+ * @return bool
+ */
+ public function isGeneratorDone() {
+ return $this->generatorDone;
+ }
+
+ /**
+ * Get the list of modules that should actually be run
+ * @return ApiBase[]
+ */
+ public function getRunModules() {
+ return array_values( array_filter( $this->allModules ) );
+ }
+
+ /**
+ * Set the continuation parameter for a module
+ * @param ApiBase $module
+ * @param string $paramName
+ * @param string|array $paramValue
+ * @throws UnexpectedValueException
+ */
+ public function addContinueParam( ApiBase $module, $paramName, $paramValue ) {
+ $name = $module->getModuleName();
+ if ( !isset( $this->allModules[$name] ) ) {
+ throw new UnexpectedValueException(
+ "Module '$name' called " . __METHOD__ .
+ ' but was not passed to ' . __CLASS__ . '::__construct'
+ );
+ }
+ if ( !$this->allModules[$name] ) {
+ throw new UnexpectedValueException(
+ "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
+ * @param ApiBase $module
+ * @param string $paramName
+ * @param string|array $paramValue
+ */
+ public function addGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
+ $name = $module->getModuleName();
+ $paramName = $module->encodeParamName( $paramName );
+ if ( is_array( $paramValue ) ) {
+ $paramValue = join( '|', $paramValue );
+ }
+ $this->generatorContinuationData[$name][$paramName] = $paramValue;
+ }
+
+ /**
+ * Fetch raw continuation data
+ * @return array
+ */
+ public function getRawContinuation() {
+ return array_merge_recursive( $this->continuationData, $this->generatorContinuationData );
+ }
+
+ /**
+ * Fetch continuation result data
+ * @return array Array( (array)$data, (bool)$batchcomplete )
+ */
+ public function getContinuation() {
+ $data = array();
+ $batchcomplete = false;
+
+ $finishedModules = array_diff(
+ array_keys( $this->allModules ),
+ array_keys( $this->continuationData )
+ );
+
+ // First, grab the non-generator-using continuation data
+ $continuationData = array_diff_key( $this->continuationData, $this->generatedModules );
+ foreach ( $continuationData as $module => $kvp ) {
+ $data += $kvp;
+ }
+
+ // Next, handle the generator-using continuation data
+ $continuationData = array_intersect_key( $this->continuationData, $this->generatedModules );
+ if ( $continuationData ) {
+ // Some modules are unfinished: include those params, and copy
+ // the generator params.
+ foreach ( $continuationData as $module => $kvp ) {
+ $data += $kvp;
+ }
+ $data += $this->generatorParams;
+ $generatorKeys = join( '|', array_keys( $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
+ $generatorParams = array();
+ foreach ( $this->generatorContinuationData as $kvp ) {
+ $generatorParams += $kvp;
+ }
+ $data += $generatorParams;
+ $finishedModules = array_diff( $finishedModules, $this->generatedModules );
+ $generatorKeys = join( '|', array_keys( $generatorParams ) );
+ $batchcomplete = true;
+ } else {
+ // Generator and prop modules are all done. Mark it so.
+ $generatorKeys = '-';
+ $batchcomplete = true;
+ }
+
+ // Set 'continue' if any continuation data is set or if the generator
+ // still needs to run
+ if ( $data || $generatorKeys !== '-' ) {
+ $data['continue'] = $generatorKeys . '||' . join( '|', $finishedModules );
+ }
+
+ return array( $data, $batchcomplete );
+ }
+
+ /**
+ * Store the continuation data into the result
+ * @param ApiResult $result
+ */
+ public function setContinuationIntoResult( ApiResult $result ) {
+ list( $data, $batchcomplete ) = $this->getContinuation();
+ if ( $data ) {
+ $result->addValue( null, 'continue', $data,
+ ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+ if ( $batchcomplete ) {
+ $result->addValue( null, 'batchcomplete', true,
+ ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+ }
+}
diff --git a/includes/api/ApiCreateAccount.php b/includes/api/ApiCreateAccount.php
index 2ce532b9..455540b4 100644
--- a/includes/api/ApiCreateAccount.php
+++ b/includes/api/ApiCreateAccount.php
@@ -29,9 +29,12 @@
*/
class ApiCreateAccount extends ApiBase {
public function execute() {
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
- $this->dieUsage( 'Cannot create account when using a callback', 'aborted' );
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
+ $this->dieUsage(
+ 'Cannot create account when the same-origin policy is not applied', 'aborted'
+ );
}
// $loginForm->addNewaccountInternal will throw exceptions
@@ -83,7 +86,7 @@ class ApiCreateAccount extends ApiBase {
$loginForm = new LoginForm();
$loginForm->setContext( $context );
- wfRunHooks( 'AddNewAccountApiForm', array( $this, $loginForm ) );
+ Hooks::run( 'AddNewAccountApiForm', array( $this, $loginForm ) );
$loginForm->load();
$status = $loginForm->addNewaccountInternal();
@@ -113,7 +116,7 @@ class ApiCreateAccount extends ApiBase {
// Save settings (including confirmation token)
$user->saveSettings();
- wfRunHooks( 'AddNewAccount', array( $user, $params['mailpassword'] ) );
+ Hooks::run( 'AddNewAccount', array( $user, $params['mailpassword'] ) );
if ( $params['mailpassword'] ) {
$logAction = 'byemail';
@@ -149,9 +152,9 @@ class ApiCreateAccount extends ApiBase {
$warnings = $status->getErrorsByType( 'warning' );
if ( $warnings ) {
foreach ( $warnings as &$warning ) {
- $apiResult->setIndexedTagName( $warning['params'], 'param' );
+ ApiResult::setIndexedTagName( $warning['params'], 'param' );
}
- $apiResult->setIndexedTagName( $warnings, 'warning' );
+ ApiResult::setIndexedTagName( $warnings, 'warning' );
$result['warnings'] = $warnings;
}
} else {
@@ -160,15 +163,11 @@ class ApiCreateAccount extends ApiBase {
}
// Give extensions a chance to modify the API result data
- wfRunHooks( 'AddNewAccountApiResult', array( $this, $loginForm, &$result ) );
+ Hooks::run( 'AddNewAccountApiResult', array( $this, $loginForm, &$result ) );
$apiResult->addValue( null, 'createaccount', $result );
}
- public function getDescription() {
- return 'Create a new user account.';
- }
-
public function mustBePosted() {
return true;
}
@@ -204,27 +203,12 @@ class ApiCreateAccount extends ApiBase {
);
}
- public function getParamDescription() {
- $p = $this->getModulePrefix();
-
- return array(
- 'name' => 'Username',
- 'password' => "Password (ignored if {$p}mailpassword is set)",
- 'domain' => 'Domain for external authentication (optional)',
- 'token' => 'Account creation token obtained in first request',
- 'email' => 'Email address of user (optional)',
- '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 getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=createaccount&name=testuser&password=test123',
- 'api.php?action=createaccount&name=testmailuser&mailpassword=true&reason=MyReason',
+ 'action=createaccount&name=testuser&password=test123'
+ => 'apihelp-createaccount-example-pass',
+ 'action=createaccount&name=testmailuser&mailpassword=true&reason=MyReason'
+ => 'apihelp-createaccount-example-mail',
);
}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index abca8245..d8b57182 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -210,35 +210,16 @@ 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",
- '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',
- 'unwatch' => 'Remove the page from your watchlist',
- 'oldimage' => 'The name of the old image to delete as provided by iiprop=archivename'
- );
- }
-
- public function getDescription() {
- return 'Delete a page.';
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
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"',
+ 'action=delete&title=Main%20Page&token=123ABC'
+ => 'apihelp-delete-example-simple',
+ 'action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move'
+ => 'apihelp-delete-example-reason',
);
}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index 6ea5d202..fc975220 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -44,19 +44,7 @@ class ApiDisabled extends ApiBase {
return false;
}
- public function getAllowedParams() {
- return array();
- }
-
- public function getParamDescription() {
- return array();
- }
-
- public function getDescription() {
- return 'This module has been disabled.';
- }
-
- public function getExamples() {
- return array();
+ protected function getDescriptionMessage() {
+ return 'apihelp-disabled-description';
}
}
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index a423b560..54a83915 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -28,7 +28,9 @@
* A module that allows for editing and creating pages.
*
* Currently, this wraps around the EditPage class in an ugly way,
- * EditPage.php should be rewritten to provide a cleaner interface
+ * EditPage.php should be rewritten to provide a cleaner interface,
+ * see T20654 if you're inspired to fix this.
+ *
* @ingroup API
*/
class ApiEditPage extends ApiBase {
@@ -48,8 +50,12 @@ 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 ( $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;
@@ -76,7 +82,7 @@ class ApiEditPage extends ApiBase {
$titleObj = $newTitle;
}
- $apiResult->setIndexedTagName( $redirValues, 'r' );
+ ApiResult::setIndexedTagName( $redirValues, 'r' );
$apiResult->addValue( null, 'redirects', $redirValues );
// Since the page changed, update $pageObj
@@ -195,9 +201,9 @@ class ApiEditPage extends ApiBase {
list( $params['undo'], $params['undoafter'] ) =
array( $params['undoafter'], $params['undo'] );
}
- $undoafterRev = Revision::newFromID( $params['undoafter'] );
+ $undoafterRev = Revision::newFromId( $params['undoafter'] );
}
- $undoRev = Revision::newFromID( $params['undo'] );
+ $undoRev = Revision::newFromId( $params['undo'] );
if ( is_null( $undoRev ) || $undoRev->isDeleted( Revision::DELETED_TEXT ) ) {
$this->dieUsageMsg( array( 'nosuchrevid', $params['undo'] ) );
}
@@ -252,8 +258,10 @@ class ApiEditPage extends ApiBase {
'format' => $contentFormat,
'model' => $contentHandler->getModelID(),
'wpEditToken' => $params['token'],
- 'wpIgnoreBlankSummary' => '',
- 'wpIgnoreBlankArticle' => true
+ 'wpIgnoreBlankSummary' => true,
+ 'wpIgnoreBlankArticle' => true,
+ 'wpIgnoreSelfRedirect' => true,
+ 'bot' => $params['bot'],
);
if ( !is_null( $params['summary'] ) ) {
@@ -294,10 +302,13 @@ class ApiEditPage extends ApiBase {
if ( !is_null( $params['section'] ) ) {
$section = $params['section'];
if ( !preg_match( '/^((T-)?\d+|new)$/', $section ) ) {
- $this->dieUsage( "The section parameter must be a valid section id or 'new'", "invalidsection" );
+ $this->dieUsage( "The section parameter must be a valid section id or 'new'",
+ "invalidsection" );
}
$content = $pageObj->getContent();
- if ( $section !== '0' && $section != 'new' && ( !$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'];
@@ -320,6 +331,15 @@ class ApiEditPage extends ApiBase {
$requestArray['wpWatchthis'] = '';
}
+ // Apply change tags
+ if ( count( $params['tags'] ) ) {
+ if ( $user->isAllowed( 'applychangetags' ) ) {
+ $requestArray['wpChangeTags'] = implode( ',', $params['tags'] );
+ } else {
+ $this->dieUsage( 'You don\'t have permission to set change tags.', 'taggingnotallowed' );
+ }
+ }
+
// Pass through anything else we might have been given, to support extensions
// This is kind of a hack but it's the best we can do to make extensions work
$requestArray += $this->getRequest()->getValues();
@@ -381,7 +401,7 @@ class ApiEditPage extends ApiBase {
// Run hooks
// Handle APIEditBeforeSave parameters
$r = array();
- if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $content, &$r ) ) ) {
+ if ( !Hooks::run( 'APIEditBeforeSave', array( $ep, $content, &$r ) ) ) {
if ( count( $r ) ) {
$r['result'] = 'Failure';
$apiResult->addValue( null, $this->getModuleName(), $r );
@@ -400,13 +420,20 @@ class ApiEditPage extends ApiBase {
$oldRequest = $wgRequest;
$wgRequest = $req;
- $status = $ep->internalAttemptSave( $result, $user->isAllowed( 'bot' ) && $params['bot'] );
+ $status = $ep->attemptSave( $result );
$wgRequest = $oldRequest;
switch ( $status->value ) {
case EditPage::AS_HOOK_ERROR:
case EditPage::AS_HOOK_ERROR_EXPECTED:
- $this->dieUsageMsg( 'hookaborted' );
+ if ( isset( $status->apiHookResult ) ) {
+ $r = $status->apiHookResult;
+ $r['result'] = 'Failure';
+ $apiResult->addValue( null, $this->getModuleName(), $r );
+ return;
+ } else {
+ $this->dieUsageMsg( 'hookaborted' );
+ }
case EditPage::AS_PARSE_ERROR:
$this->dieUsage( $status->getMessage(), 'parseerror' );
@@ -454,12 +481,14 @@ class ApiEditPage extends ApiBase {
case EditPage::AS_CONFLICT_DETECTED:
$this->dieUsageMsg( 'editconflict' );
- // case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary
case EditPage::AS_TEXTBOX_EMPTY:
$this->dieUsageMsg( 'emptynewsection' );
+ case EditPage::AS_CHANGE_TAG_ERROR:
+ $this->dieStatus( $status );
+
case EditPage::AS_SUCCESS_NEW_ARTICLE:
- $r['new'] = '';
+ $r['new'] = true;
// fall-through
case EditPage::AS_SUCCESS_UPDATE:
@@ -469,7 +498,7 @@ class ApiEditPage extends ApiBase {
$r['contentmodel'] = $titleObj->getContentModel();
$newRevId = $articleObject->getLatest();
if ( $newRevId == $oldRevId ) {
- $r['nochange'] = '';
+ $r['nochange'] = true;
} else {
$r['oldrevid'] = intval( $oldRevId );
$r['newrevid'] = intval( $newRevId );
@@ -479,6 +508,7 @@ class ApiEditPage extends ApiBase {
break;
case EditPage::AS_SUMMARY_NEEDED:
+ // Shouldn't happen since we set wpIgnoreBlankSummary, but just in case
$this->dieUsageMsg( 'summaryrequired' );
case EditPage::AS_END:
@@ -499,10 +529,6 @@ class ApiEditPage extends ApiBase {
return true;
}
- public function getDescription() {
- return 'Create and edit pages.';
- }
-
public function getAllowedParams() {
return array(
'title' => array(
@@ -517,6 +543,10 @@ class ApiEditPage extends ApiBase {
),
'text' => null,
'summary' => null,
+ 'tags' => array(
+ ApiBase::PARAM_TYPE => ChangeTags::listExplicitlyDefinedTags(),
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'minor' => false,
'notminor' => false,
'bot' => false,
@@ -560,58 +590,11 @@ class ApiEditPage extends ApiBase {
),
'contentmodel' => array(
ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
- )
- );
- }
-
- 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(
- /* 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",
- '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'
),
- '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'
+ 'token' => array(
+ // Standard definition automatically inserted
+ ApiBase::PARAM_HELP_MSG_APPEND => array( 'apihelp-edit-param-token' ),
),
- '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" ),
- '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',
- 'contentformat' => 'Content serialization format used for the input text',
- 'contentmodel' => 'Content model of the new content',
);
}
@@ -619,17 +602,17 @@ class ApiEditPage extends ApiBase {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- '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\\'
- => 'Prepend __NOTOC__ to a page (anonymous user)',
- 'api.php?action=edit&title=Test&undo=13585&undoafter=13579&' .
- 'basetimestamp=20070824123454&token=%2B\\'
- => 'Undo r13579 through r13585 with autosummary (anonymous user)',
+ 'action=edit&title=Test&summary=test%20summary&' .
+ 'text=article%20content&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
+ => 'apihelp-edit-example-edit',
+ 'action=edit&title=Test&summary=NOTOC&minor=&' .
+ 'prependtext=__NOTOC__%0A&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
+ => 'apihelp-edit-example-prepend',
+ 'action=edit&title=Test&undo=13585&undoafter=13579&' .
+ 'basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
+ => 'apihelp-edit-example-undo',
);
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index 9870b2de..15eb475e 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -102,27 +102,14 @@ class ApiEmailUser extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'target' => 'User to send email to',
- 'subject' => 'Subject header',
- 'text' => 'Mail body',
- 'ccme' => 'Send a copy of this mail to me',
- );
- }
-
- public function getDescription() {
- return 'Email a user.';
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=emailuser&target=WikiSysop&text=Content&token=123ABC'
- => 'Send an email to the User "WikiSysop" with the text "Content"',
+ 'action=emailuser&target=WikiSysop&text=Content&token=123ABC'
+ => 'apihelp-emailuser-example-email',
);
}
diff --git a/includes/api/ApiErrorFormatter.php b/includes/api/ApiErrorFormatter.php
new file mode 100644
index 00000000..94143291
--- /dev/null
+++ b/includes/api/ApiErrorFormatter.php
@@ -0,0 +1,303 @@
+<?php
+/**
+ * This file contains the ApiErrorFormatter definition, plus implementations of
+ * specific formatters.
+ *
+ * This 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 errors and warnings for the API, and add them to the associated
+ * ApiResult.
+ * @since 1.25
+ * @ingroup API
+ */
+class ApiErrorFormatter {
+ /** @var Title Dummy title to silence warnings from MessageCache::parse() */
+ private static $dummyTitle = null;
+
+ /** @var ApiResult */
+ protected $result;
+
+ /** @var Language */
+ protected $lang;
+ protected $useDB = false;
+ protected $format = 'none';
+
+ /**
+ * @param ApiResult $result Into which data will be added
+ * @param Language $lang Used for i18n
+ * @param string $format
+ * - text: Error message as wikitext
+ * - html: Error message as HTML
+ * - raw: Raw message key and parameters, no human-readable text
+ * - none: Code and data only, no human-readable text
+ * @param bool $useDB Whether to use local translations for errors and warnings.
+ */
+ public function __construct( ApiResult $result, Language $lang, $format, $useDB = false ) {
+ $this->result = $result;
+ $this->lang = $lang;
+ $this->useDB = $useDB;
+ $this->format = $format;
+ }
+
+ /**
+ * Fetch a dummy title to set on Messages
+ * @return Title
+ */
+ protected function getDummyTitle() {
+ if ( self::$dummyTitle === null ) {
+ self::$dummyTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ );
+ }
+ return self::$dummyTitle;
+ }
+
+ /**
+ * Add a warning to the result
+ * @param string $moduleName
+ * @param MessageSpecifier|array|string $msg i18n message for the warning
+ * @param string $code Machine-readable code for the warning. Defaults as
+ * for IApiMessage::getApiCode().
+ * @param array $data Machine-readable data for the warning, if any.
+ * Uses IApiMessage::getApiData() if $msg implements that interface.
+ */
+ public function addWarning( $moduleName, $msg, $code = null, $data = null ) {
+ $msg = ApiMessage::create( $msg, $code, $data )
+ ->inLanguage( $this->lang )
+ ->title( $this->getDummyTitle() )
+ ->useDatabase( $this->useDB );
+ $this->addWarningOrError( 'warning', $moduleName, $msg );
+ }
+
+ /**
+ * Add an error to the result
+ * @param string $moduleName
+ * @param MessageSpecifier|array|string $msg i18n message for the error
+ * @param string $code Machine-readable code for the warning. Defaults as
+ * for IApiMessage::getApiCode().
+ * @param array $data Machine-readable data for the warning, if any.
+ * Uses IApiMessage::getApiData() if $msg implements that interface.
+ */
+ public function addError( $moduleName, $msg, $code = null, $data = null ) {
+ $msg = ApiMessage::create( $msg, $code, $data )
+ ->inLanguage( $this->lang )
+ ->title( $this->getDummyTitle() )
+ ->useDatabase( $this->useDB );
+ $this->addWarningOrError( 'error', $moduleName, $msg );
+ }
+
+ /**
+ * Add warnings and errors from a Status object to the result
+ * @param string $moduleName
+ * @param Status $status
+ * @param string[] $types 'warning' and/or 'error'
+ */
+ public function addMessagesFromStatus(
+ $moduleName, Status $status, $types = array( 'warning', 'error' )
+ ) {
+ if ( $status->isGood() || !$status->errors ) {
+ return;
+ }
+
+ $types = (array)$types;
+ foreach ( $status->errors as $error ) {
+ if ( !in_array( $error['type'], $types, true ) ) {
+ continue;
+ }
+
+ if ( $error['type'] === 'error' ) {
+ $tag = 'error';
+ } else {
+ // Assume any unknown type is a warning
+ $tag = 'warning';
+ }
+
+ if ( is_array( $error ) && isset( $error['message'] ) ) {
+ // Normal case
+ if ( $error['message'] instanceof Message ) {
+ $msg = ApiMessage::create( $error['message'], null, array() );
+ } else {
+ $args = isset( $error['params'] ) ? $error['params'] : array();
+ array_unshift( $args, $error['message'] );
+ $error += array( 'params' => array() );
+ $msg = ApiMessage::create( $args, null, array() );
+ }
+ } elseif ( is_array( $error ) ) {
+ // Weird case handled by Message::getErrorMessage
+ $msg = ApiMessage::create( $error, null, array() );
+ } else {
+ // Another weird case handled by Message::getErrorMessage
+ $msg = ApiMessage::create( $error, null, array() );
+ }
+
+ $msg->inLanguage( $this->lang )
+ ->title( $this->getDummyTitle() )
+ ->useDatabase( $this->useDB );
+ $this->addWarningOrError( $tag, $moduleName, $msg );
+ }
+ }
+
+ /**
+ * Format messages from a Status as an array
+ * @param Status $status
+ * @param string $type 'warning' or 'error'
+ * @param string|null $format
+ * @return array
+ */
+ public function arrayFromStatus( Status $status, $type = 'error', $format = null ) {
+ if ( $status->isGood() || !$status->errors ) {
+ return array();
+ }
+
+ $result = new ApiResult( 1e6 );
+ $formatter = new ApiErrorFormatter(
+ $result, $this->lang, $format ?: $this->format, $this->useDB
+ );
+ $formatter->addMessagesFromStatus( 'dummy', $status, array( $type ) );
+ switch ( $type ) {
+ case 'error':
+ return (array)$result->getResultData( array( 'errors', 'dummy' ) );
+ case 'warning':
+ return (array)$result->getResultData( array( 'warnings', 'dummy' ) );
+ }
+ }
+
+ /**
+ * Actually add the warning or error to the result
+ * @param string $tag 'warning' or 'error'
+ * @param string $moduleName
+ * @param ApiMessage|ApiRawMessage $msg
+ */
+ protected function addWarningOrError( $tag, $moduleName, $msg ) {
+ $value = array( 'code' => $msg->getApiCode() );
+ switch ( $this->format ) {
+ case 'wikitext':
+ $value += array(
+ 'text' => $msg->text(),
+ ApiResult::META_CONTENT => 'text',
+ );
+ break;
+
+ case 'html':
+ $value += array(
+ 'html' => $msg->parse(),
+ ApiResult::META_CONTENT => 'html',
+ );
+ break;
+
+ case 'raw':
+ $value += array(
+ 'message' => $msg->getKey(),
+ 'params' => $msg->getParams(),
+ );
+ ApiResult::setIndexedTagName( $value['params'], 'param' );
+ break;
+
+ case 'none':
+ break;
+ }
+ $value += $msg->getApiData();
+
+ $path = array( $tag . 's', $moduleName );
+ $existing = $this->result->getResultData( $path );
+ if ( $existing === null || !in_array( $value, $existing ) ) {
+ $flags = ApiResult::NO_SIZE_CHECK;
+ if ( $existing === null ) {
+ $flags |= ApiResult::ADD_ON_TOP;
+ }
+ $this->result->addValue( $path, null, $value, $flags );
+ $this->result->addIndexedTagName( $path, $tag );
+ }
+ }
+}
+
+/**
+ * Format errors and warnings in the old style, for backwards compatibility.
+ * @since 1.25
+ * @deprecated Only for backwards compatibility, do not use
+ * @ingroup API
+ */
+class ApiErrorFormatter_BackCompat extends ApiErrorFormatter {
+ /**
+ * @param ApiResult $result Into which data will be added
+ */
+ public function __construct( ApiResult $result ) {
+ parent::__construct( $result, Language::factory( 'en' ), 'none', false );
+ }
+
+ public function arrayFromStatus( Status $status, $type = 'error', $format = null ) {
+ if ( $status->isGood() || !$status->errors ) {
+ return array();
+ }
+
+ $result = array();
+ foreach ( $status->getErrorsByType( $type ) as $error ) {
+ if ( $error['message'] instanceof Message ) {
+ $error = array(
+ 'message' => $error['message']->getKey(),
+ 'params' => $error['message']->getParams(),
+ ) + $error;
+ }
+ ApiResult::setIndexedTagName( $error['params'], 'param' );
+ $result[] = $error;
+ }
+ ApiResult::setIndexedTagName( $result, $type );
+
+ return $result;
+ }
+
+ protected function addWarningOrError( $tag, $moduleName, $msg ) {
+ $value = $msg->plain();
+
+ if ( $tag === 'error' ) {
+ // In BC mode, only one error
+ $code = $msg->getApiCode();
+ if ( isset( ApiBase::$messageMap[$code] ) ) {
+ // Backwards compatibility
+ $code = ApiBase::$messageMap[$code]['code'];
+ }
+
+ $value = array(
+ 'code' => $code,
+ 'info' => $value,
+ ) + $msg->getApiData();
+ $this->result->addValue( null, 'error', $value,
+ ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ } else {
+ // Don't add duplicate warnings
+ $tag .= 's';
+ $path = array( $tag, $moduleName );
+ $oldWarning = $this->result->getResultData( array( $tag, $moduleName, $tag ) );
+ if ( $oldWarning !== null ) {
+ $warnPos = strpos( $oldWarning, $value );
+ // If $value was found in $oldWarning, check if it starts at 0 or after "\n"
+ if ( $warnPos !== false && ( $warnPos === 0 || $oldWarning[$warnPos - 1] === "\n" ) ) {
+ // Check if $value is followed by "\n" or the end of the $oldWarning
+ $warnPos += strlen( $value );
+ if ( strlen( $oldWarning ) <= $warnPos || $oldWarning[$warnPos] === "\n" ) {
+ return;
+ }
+ }
+ // If there is a warning already, append it to the existing one
+ $value = "$oldWarning\n$value";
+ }
+ $this->result->addContentValue( $path, $tag, $value,
+ ApiResult::OVERRIDE | ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+ }
+}
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index 8a3b534d..6d064eb2 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -52,10 +52,19 @@ class ApiExpandTemplates extends ApiBase {
$prop = array_flip( $params['prop'] );
}
- // Create title for parser
- $title_obj = Title::newFromText( $params['title'] );
- if ( !$title_obj || $title_obj->isExternal() ) {
- $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ // Get title and revision ID for parser
+ $revid = $params['revid'];
+ if ( $revid !== null ) {
+ $rev = Revision::newFromId( $revid );
+ if ( !$rev ) {
+ $this->dieUsage( "There is no revision ID $revid", 'missingrev' );
+ }
+ $title_obj = $rev->getTitle();
+ } else {
+ $title_obj = Title::newFromText( $params['title'] );
+ if ( !$title_obj || $title_obj->isExternal() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
}
$result = $this->getResult();
@@ -75,7 +84,7 @@ class ApiExpandTemplates extends ApiBase {
$this->logFeatureUsage( 'action=expandtemplates&generatexml' );
}
- $wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
+ $wgParser->startExternalParse( $title_obj, $options, Parser::OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $params['text'] );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
@@ -87,38 +96,45 @@ class ApiExpandTemplates extends ApiBase {
$retval['parsetree'] = $xml;
} else {
// the old way
- $xml_result = array();
- ApiResult::setContent( $xml_result, $xml );
- $result->addValue( null, 'parsetree', $xml_result );
+ $result->addValue( null, 'parsetree', $xml );
+ $result->addValue( null, ApiResult::META_BC_SUBELEMENTS, array( 'parsetree' ) );
}
}
// 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 );
+ $wgParser->startExternalParse( $title_obj, $options, Parser::OT_PREPROCESS );
$frame = $wgParser->getPreprocessor()->newFrame();
- $wikitext = $wgParser->preprocess( $params['text'], $title_obj, $options, null, $frame );
+ $wikitext = $wgParser->preprocess( $params['text'], $title_obj, $options, $revid, $frame );
if ( $params['prop'] === null ) {
// the old way
- ApiResult::setContent( $retval, $wikitext );
+ ApiResult::setContentValue( $retval, 'wikitext', $wikitext );
} else {
if ( isset( $prop['categories'] ) ) {
$categories = $wgParser->getOutput()->getCategories();
- if ( !empty( $categories ) ) {
+ if ( $categories ) {
$categories_result = array();
foreach ( $categories as $category => $sortkey ) {
$entry = array();
$entry['sortkey'] = $sortkey;
- ApiResult::setContent( $entry, $category );
+ ApiResult::setContentValue( $entry, 'category', $category );
$categories_result[] = $entry;
}
- $result->setIndexedTagName( $categories_result, 'category' );
+ ApiResult::setIndexedTagName( $categories_result, 'category' );
$retval['categories'] = $categories_result;
}
}
- if ( isset( $prop['volatile'] ) && $frame->isVolatile() ) {
- $retval['volatile'] = '';
+ if ( isset( $prop['properties'] ) ) {
+ $properties = $wgParser->getOutput()->getProperties();
+ if ( $properties ) {
+ ApiResult::setArrayType( $properties, 'BCkvp', 'name' );
+ ApiResult::setIndexedTagName( $properties, 'property' );
+ $retval['properties'] = $properties;
+ }
+ }
+ if ( isset( $prop['volatile'] ) ) {
+ $retval['volatile'] = $frame->isVolatile();
}
if ( isset( $prop['ttl'] ) && $frame->getTTL() !== null ) {
$retval['ttl'] = $frame->getTTL();
@@ -128,7 +144,7 @@ class ApiExpandTemplates extends ApiBase {
}
}
}
- $result->setSubelements( $retval, array( 'wikitext', 'parsetree' ) );
+ ApiResult::setSubelementsList( $retval, array( 'wikitext', 'parsetree' ) );
$result->addValue( null, $this->getModuleName(), $retval );
}
@@ -141,10 +157,14 @@ class ApiExpandTemplates extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
),
+ 'revid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
'prop' => array(
ApiBase::PARAM_TYPE => array(
'wikitext',
'categories',
+ 'properties',
'volatile',
'ttl',
'parsetree',
@@ -159,35 +179,10 @@ class ApiExpandTemplates extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'text' => 'Wikitext to convert',
- 'title' => 'Title of page',
- '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',
- 'generatexml' => 'Generate XML parse tree (replaced by prop=parsetree)',
- );
- }
-
- public function getDescription() {
- return 'Expands all templates in wikitext.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=expandtemplates&text={{Project:Sandbox}}'
+ 'action=expandtemplates&text={{Project:Sandbox}}'
+ => 'apihelp-expandtemplates-example-simple',
);
}
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
index 374203eb..edda6723 100644
--- a/includes/api/ApiFeedContributions.php
+++ b/includes/api/ApiFeedContributions.php
@@ -95,7 +95,10 @@ class ApiFeedContributions extends ApiBase {
if ( ++$count > $limit ) {
break;
}
- $feedItems[] = $this->feedItem( $row );
+ $item = $this->feedItem( $row );
+ if ( $item !== null ) {
+ $feedItems[] = $item;
+ }
}
}
@@ -103,6 +106,23 @@ class ApiFeedContributions extends ApiBase {
}
protected function feedItem( $row ) {
+ // This hook is the api contributions equivalent to the
+ // ContributionsLineEnding hook. Hook implementers may cancel
+ // the hook to signal the user is not allowed to read this item.
+ $feedItem = null;
+ $hookResult = Hooks::run(
+ 'ApiFeedContributions::feedItem',
+ array( $row, $this->getContext(), &$feedItem )
+ );
+ // Hook returned a valid feed item
+ if ( $feedItem instanceof FeedItem ) {
+ return $feedItem;
+ // Hook was canceled and did not return a valid feed item
+ } elseif ( !$hookResult ) {
+ return null;
+ }
+
+ // Hook completed and did not return a valid feed item
$title = Title::makeTitle( intval( $row->page_namespace ), $row->page_title );
if ( $title && $title->userCan( 'read', $this->getUser() ) ) {
$date = $row->rev_timestamp;
@@ -161,7 +181,7 @@ class ApiFeedContributions extends ApiBase {
public function getAllowedParams() {
$feedFormatNames = array_keys( $this->getConfig()->get( 'FeedClasses' ) );
- return array(
+ $ret = array(
'feedformat' => array(
ApiBase::PARAM_DFLT => 'rss',
ApiBase::PARAM_TYPE => $feedFormatNames
@@ -187,32 +207,22 @@ class ApiFeedContributions extends ApiBase {
'deletedonly' => false,
'toponly' => false,
'newonly' => false,
- 'showsizediff' => false,
+ 'showsizediff' => array(
+ ApiBase::PARAM_DFLT => false,
+ ),
);
- }
- public function getParamDescription() {
- return array(
- 'feedformat' => 'The format of the feed',
- 'user' => 'What users to get the contributions for',
- 'namespace' => 'What namespace to filter the contributions by',
- 'year' => 'From year (and earlier)',
- 'month' => 'From month (and earlier)',
- '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',
- );
- }
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['showsizediff'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
+ }
- public function getDescription() {
- return 'Returns a user contributions feed.';
+ return $ret;
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=feedcontributions&user=Reedy',
+ 'action=feedcontributions&user=Example'
+ => 'apihelp-feedcontributions-example-simple',
);
}
}
diff --git a/includes/api/ApiFeedRecentChanges.php b/includes/api/ApiFeedRecentChanges.php
index 7239a296..d452bbd6 100644
--- a/includes/api/ApiFeedRecentChanges.php
+++ b/includes/api/ApiFeedRecentChanges.php
@@ -171,37 +171,12 @@ class ApiFeedRecentChanges extends ApiBase {
return $ret;
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
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'
+ 'action=feedrecentchanges'
+ => 'apihelp-feedrecentchanges-example-simple',
+ 'action=feedrecentchanges&days=30'
+ => 'apihelp-feedrecentchanges-example-30days',
);
}
}
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index 6aef8fc2..d1beef8a 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -112,12 +112,16 @@ class ApiFeedWatchlist extends ApiBase {
$module = new ApiMain( $fauxReq );
$module->execute();
- // Get data array
- $data = $module->getResultData();
-
+ $data = $module->getResult()->getResultData( array( 'query', 'watchlist' ) );
$feedItems = array();
- foreach ( (array)$data['query']['watchlist'] as $info ) {
- $feedItems[] = $this->createFeedItem( $info );
+ foreach ( (array)$data as $key => $info ) {
+ if ( ApiResult::isMetadataKey( $key ) ) {
+ continue;
+ }
+ $feedItem = $this->createFeedItem( $info );
+ if ( $feedItem ) {
+ $feedItems[] = $feedItem;
+ }
}
$msg = wfMessage( 'watchlist' )->inContentLanguage()->text();
@@ -166,10 +170,22 @@ class ApiFeedWatchlist extends ApiBase {
private function createFeedItem( $info ) {
$titleStr = $info['title'];
$title = Title::newFromText( $titleStr );
+ $curidParam = array();
+ if ( !$title || $title->isExternal() ) {
+ // Probably a formerly-valid title that's now conflicting with an
+ // interwiki prefix or the like.
+ if ( isset( $info['pageid'] ) ) {
+ $title = Title::newFromId( $info['pageid'] );
+ $curidParam = array( 'curid' => $info['pageid'] );
+ }
+ if ( !$title || $title->isExternal() ) {
+ return null;
+ }
+ }
if ( isset( $info['revid'] ) ) {
$titleUrl = $title->getFullURL( array( 'diff' => $info['revid'] ) );
} else {
- $titleUrl = $title->getFullURL();
+ $titleUrl = $title->getFullURL( $curidParam );
}
$comment = isset( $info['comment'] ) ? $info['comment'] : null;
@@ -219,50 +235,42 @@ class ApiFeedWatchlist extends ApiBase {
),
'linktosections' => false,
);
+
+ $copyParams = array(
+ 'allrev' => 'allrev',
+ 'owner' => 'wlowner',
+ 'token' => 'wltoken',
+ 'show' => 'wlshow',
+ 'type' => 'wltype',
+ 'excludeuser' => 'wlexcludeuser',
+ );
if ( $flags ) {
$wlparams = $this->getWatchlistModule()->getAllowedParams( $flags );
- $ret['allrev'] = $wlparams['allrev'];
- $ret['wlowner'] = $wlparams['owner'];
- $ret['wltoken'] = $wlparams['token'];
- $ret['wlshow'] = $wlparams['show'];
- $ret['wltype'] = $wlparams['type'];
- $ret['wlexcludeuser'] = $wlparams['excludeuser'];
+ foreach ( $copyParams as $from => $to ) {
+ $p = $wlparams[$from];
+ if ( !is_array( $p ) ) {
+ $p = array( ApiBase::PARAM_DFLT => $p );
+ }
+ if ( !isset( $p[ApiBase::PARAM_HELP_MSG] ) ) {
+ $p[ApiBase::PARAM_HELP_MSG] = "apihelp-query+watchlist-param-$from";
+ }
+ $ret[$to] = $p;
+ }
} else {
- $ret['allrev'] = null;
- $ret['wlowner'] = null;
- $ret['wltoken'] = null;
- $ret['wlshow'] = null;
- $ret['wltype'] = null;
- $ret['wlexcludeuser'] = null;
+ foreach ( $copyParams as $from => $to ) {
+ $ret[$to] = 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',
- 'linktosections' => 'Link directly to changed sections if possible',
- 'allrev' => $wldescr['allrev'],
- 'wlowner' => $wldescr['owner'],
- 'wltoken' => $wldescr['token'],
- 'wlshow' => $wldescr['show'],
- 'wltype' => $wldescr['type'],
- 'wlexcludeuser' => $wldescr['excludeuser'],
- );
- }
-
- public function getDescription() {
- return 'Returns a watchlist feed.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=feedwatchlist',
- 'api.php?action=feedwatchlist&allrev=&hours=6'
+ 'action=feedwatchlist'
+ => 'apihelp-feedwatchlist-example-default',
+ 'action=feedwatchlist&allrev=&hours=6'
+ => 'apihelp-feedwatchlist-example-all6hrs',
);
}
diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php
index f518e172..5517ee08 100644
--- a/includes/api/ApiFileRevert.php
+++ b/includes/api/ApiFileRevert.php
@@ -61,7 +61,7 @@ class ApiFileRevert extends ApiBase {
} else {
$result = array(
'result' => 'Failure',
- 'errors' => $this->getResult()->convertStatusToArray( $status ),
+ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $status ),
);
}
@@ -135,29 +135,15 @@ class ApiFileRevert extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'filename' => 'Target filename without the File: prefix',
- 'comment' => 'Upload comment',
- 'archivename' => 'Archive name of the revision to revert to',
- );
- }
-
- public function getDescription() {
- return array(
- 'Revert a file to an old version.'
- );
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&' .
+ 'action=filerevert&filename=Wiki.png&comment=Revert&' .
'archivename=20110305152740!Wiki.png&token=123ABC'
- => 'Revert Wiki.png to the version of 20110305152740',
+ => 'apihelp-filerevert-example-revert',
);
}
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 9165ce88..d078dc45 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -30,8 +30,9 @@
* @ingroup API
*/
abstract class ApiFormatBase extends ApiBase {
- private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared;
- private $mBufferResult = false, $mBuffer, $mDisabled = false;
+ private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp;
+ private $mBuffer, $mDisabled = false;
+ protected $mForceDefaultParams = false;
/**
* If $format ends with 'fm', pretty-print the output in HTML.
@@ -48,25 +49,19 @@ abstract class ApiFormatBase extends ApiBase {
$this->mFormat = $format;
}
$this->mFormat = strtoupper( $this->mFormat );
- $this->mCleared = false;
}
/**
* Overriding class returns the MIME type that should be sent to the client.
- * This method is not called if getIsHtml() returns true.
+ *
+ * When getIsHtml() returns true, the return value here is used for syntax
+ * highlighting but the client sees text/html.
+ *
* @return string
*/
abstract public function getMimeType();
/**
- * Whether this formatter needs raw data such as _element tags
- * @return bool
- */
- public function getNeedsRawData() {
- return false;
- }
-
- /**
* Get the internal format name
* @return string
*/
@@ -75,19 +70,6 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
- * Specify whether or not sequences like &amp;quot; should be unescaped
- * to &quot; . This should only be set to true for the help message
- * when rendered in the default (xmlfm) format. This is a temporary
- * special-case fix that should be removed once the help has been
- * reworked to use a fully HTML interface.
- *
- * @param bool $b Whether or not ampersands should be escaped.
- */
- public function setUnescapeAmps( $b ) {
- $this->mUnescapeAmps = $b;
- }
-
- /**
* Returns true when the HTML pretty-printer should be used.
* The default implementation assumes that formats ending with 'fm'
* should be formatted in HTML.
@@ -98,30 +80,27 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
- * Whether this formatter can format the help message in a nice way.
- * By default, this returns the same as getIsHtml().
- * When action=help is set explicitly, the help will always be shown
- * @return bool
- */
- public function getWantsHelp() {
- return $this->getIsHtml();
- }
-
- /**
- * Disable the formatter completely. This causes calls to initPrinter(),
- * printText() and closePrinter() to be ignored.
+ * Disable the formatter.
+ *
+ * This causes calls to initPrinter() and closePrinter() to be ignored.
*/
public function disable() {
$this->mDisabled = true;
}
+ /**
+ * Whether the printer is disabled
+ * @return bool
+ */
public function isDisabled() {
return $this->mDisabled;
}
/**
- * Whether this formatter can handle printing API errors. If this returns
- * false, then on API errors the default printer will be instantiated.
+ * 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
*/
@@ -130,24 +109,47 @@ abstract class ApiFormatBase extends ApiBase {
}
/**
- * 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,
- * except for help screens (caused by either an error in the API parameters,
- * the calling of action=help, or requesting the root script api.php).
- * @param bool $isHelpScreen Whether a help screen is going to be shown
+ * Ignore request parameters, force a default.
+ *
+ * Used as a fallback if errors are being thrown.
+ * @since 1.26
*/
- function initPrinter( $isHelpScreen ) {
+ public function forceDefaultParams() {
+ $this->mForceDefaultParams = true;
+ }
+
+ /**
+ * Overridden to honor $this->forceDefaultParams(), if applicable
+ * @since 1.26
+ */
+ protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
+ if ( !$this->mForceDefaultParams ) {
+ return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
+ }
+
+ if ( !is_array( $paramSettings ) ) {
+ return $paramSettings;
+ } elseif ( isset( $paramSettings[self::PARAM_DFLT] ) ) {
+ return $paramSettings[self::PARAM_DFLT];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Initialize the printer function and prepare the output headers.
+ * @param bool $unused Always false since 1.25
+ */
+ function initPrinter( $unused = false ) {
if ( $this->mDisabled ) {
return;
}
- $isHtml = $this->getIsHtml();
- $mime = $isHtml ? 'text/html' : $this->getMimeType();
- $script = wfScript( 'api' );
+
+ $mime = $this->getIsHtml() ? 'text/html' : $this->getMimeType();
// Some printers (ex. Feed) do their own header settings,
// in which case $mime will be set to null
- if ( is_null( $mime ) ) {
+ if ( $mime === null ) {
return; // skip any initialization
}
@@ -158,91 +160,64 @@ abstract class ApiFormatBase extends ApiBase {
if ( $apiFrameOptions ) {
$this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
}
-
- if ( $isHtml ) {
-?>
-<!DOCTYPE HTML>
-<html>
-<head>
-<?php
- if ( $this->mUnescapeAmps ) {
-?> <title>MediaWiki API</title>
-<?php
- } else {
-?> <title>MediaWiki API Result</title>
-<?php
- }
-?>
-</head>
-<body>
-<?php
- if ( !$isHelpScreen ) {
-// @codingStandardsIgnoreStart Exclude long line from CodeSniffer checks
-?>
-<br />
-<small>
-You are looking at the HTML representation of the <?php echo $this->mFormat; ?> format.<br />
-HTML is good for debugging, but is unsuitable for application use.<br />
-Specify the format parameter to change the output format.<br />
-To see the non HTML representation of the <?php echo $this->mFormat; ?> format, set format=<?php echo strtolower( $this->mFormat ); ?>.<br />
-See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or
-<a href='<?php echo $script; ?>'>API help</a> for more information.
-</small>
-<pre style='white-space: pre-wrap;'>
-<?php
-// @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
- }
- }
}
/**
- * Finish printing. Closes HTML tags.
+ * Finish printing and output buffered data.
*/
public function closePrinter() {
if ( $this->mDisabled ) {
return;
}
- if ( $this->getIsHtml() ) {
-?>
-</pre>
-</body>
-</html>
-<?php
+ $mime = $this->getMimeType();
+ if ( $this->getIsHtml() && $mime !== null ) {
+ $format = $this->getFormat();
+ $result = $this->getBuffer();
+
+ $context = new DerivativeContext( $this->getMain() );
+ $context->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'apioutput' ) );
+ $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
+ $out = new OutputPage( $context );
+ $context->setOutput( $out );
+
+ $out->addModules( 'mediawiki.apipretty' );
+ $out->setPageTitle( $context->msg( 'api-format-title' ) );
+
+ $header = $context->msg( 'api-format-prettyprint-header' )
+ ->params( $format, strtolower( $format ) )
+ ->parseAsBlock();
+ $out->addHTML(
+ Html::rawElement( 'div', array( 'class' => 'api-pretty-header' ),
+ ApiHelp::fixHelpLinks( $header )
+ )
+ );
+
+ if ( Hooks::run( 'ApiFormatHighlight', array( $context, $result, $mime, $format ) ) ) {
+ $out->addHTML(
+ Html::element( 'pre', array( 'class' => 'api-pretty-content' ), $result )
+ );
+ }
+
+ // API handles its own clickjacking protection.
+ // Note, that $wgBreakFrames will still override $wgApiFrameOptions for format mode.
+ $out->allowClickJacking();
+ $out->output();
+ } else {
+ // For non-HTML output, clear all errors that might have been
+ // displayed if display_errors=On
+ ob_clean();
+
+ echo $this->getBuffer();
}
}
/**
- * 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'.
+ * Append text to the output buffer.
* @param string $text
*/
public function printText( $text ) {
- if ( $this->mDisabled ) {
- return;
- }
- if ( $this->mBufferResult ) {
- $this->mBuffer = $text;
- } elseif ( $this->getIsHtml() ) {
- echo $this->formatHTML( $text );
- } else {
- // For non-HTML output, clear all errors that might have been
- // displayed if display_errors=On
- // Do this only once, of course
- if ( !$this->mCleared ) {
- ob_clean();
- $this->mCleared = true;
- }
- echo $text;
- }
+ $this->mBuffer .= $text;
}
/**
@@ -253,34 +228,89 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
return $this->mBuffer;
}
+ protected function getExamplesMessages() {
+ return array(
+ 'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
+ => array( 'apihelp-format-example-generic', $this->getFormat() )
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Data_formats';
+ }
+
/**
- * Set the flag to buffer the result instead of printing it.
- * @param bool $value
+ * 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 setBufferResult( $value ) {
- $this->mBufferResult = $value;
+ 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." );
+ }
+
+ /************************************************************************//**
+ * @name Deprecated
+ * @{
+ */
+
+ /**
+ * Specify whether or not sequences like &amp;quot; should be unescaped
+ * to &quot; . This should only be set to true for the help message
+ * when rendered in the default (xmlfm) format. This is a temporary
+ * special-case fix that should be removed once the help has been
+ * reworked to use a fully HTML interface.
+ *
+ * @deprecated since 1.25
+ * @param bool $b Whether or not ampersands should be escaped.
+ */
+ public function setUnescapeAmps( $b ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ $this->mUnescapeAmps = $b;
+ }
+
+ /**
+ * Whether this formatter can format the help message in a nice way.
+ * By default, this returns the same as getIsHtml().
+ * When action=help is set explicitly, the help will always be shown
+ * @deprecated since 1.25
+ * @return bool
+ */
+ public function getWantsHelp() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return $this->getIsHtml();
}
/**
* Sets whether the pretty-printer should format *bold*
+ * @deprecated since 1.25
* @param bool $help
*/
public function setHelp( $help = true ) {
+ wfDeprecated( __METHOD__, '1.25' );
$this->mHelp = $help;
}
/**
* Pretty-print various elements in HTML format, such as xml tags and
* URLs. This method also escapes characters like <
+ * @deprecated since 1.25
* @param string $text
* @return string
*/
protected function formatHTML( $text ) {
+ wfDeprecated( __METHOD__, '1.25' );
+
// Escape everything first for full coverage
$text = htmlspecialchars( $text );
- // encode all comments or tags as safe blue strings
- $text = str_replace( '&lt;', '<span style="color:blue;">&lt;', $text );
- $text = str_replace( '&gt;', '&gt;</span>', $text );
+
+ if ( $this->mFormat === 'XML' || $this->mFormat === 'WDDX' ) {
+ // encode all comments or tags as safe blue strings
+ $text = str_replace( '&lt;', '<span style="color:blue;">&lt;', $text );
+ $text = str_replace( '&gt;', '&gt;</span>', $text );
+ }
// identify requests to api.php
$text = preg_replace( '#^(\s*)(api\.php\?[^ <\n\t]+)$#m', '\1<a href="\2">\2</a>', $text );
@@ -325,30 +355,42 @@ See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>,
return $text;
}
- public function getExamples() {
- return array(
- 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
- => "Format the query result in the {$this->getModuleName()} format",
- );
- }
-
- public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:Data_formats';
- }
-
+ /**
+ * @see ApiBase::getDescription
+ * @deprecated since 1.25
+ */
public function getDescription() {
return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
}
/**
- * 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.
+ * Set the flag to buffer the result instead of printing it.
+ * @deprecated since 1.25, output is always buffered
+ * @param bool $value
*/
- 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." );
+ public function setBufferResult( $value ) {
}
+
+ /**
+ * Formerly indicated whether the formatter needed metadata from ApiResult.
+ *
+ * ApiResult previously (indirectly) used this to decide whether to add
+ * metadata or to ignore calls to metadata-setting methods, which
+ * unfortunately made several methods that should have been static have to
+ * be dynamic instead. Now ApiResult always stores metadata and formatters
+ * are required to ignore it or filter it out.
+ *
+ * @deprecated since 1.25
+ * @return bool
+ */
+ public function getNeedsRawData() {
+ return false;
+ }
+
+ /**@}*/
}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index 5ec518b3..7d359ad4 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -40,10 +40,15 @@ class ApiFormatDbg extends ApiFormatBase {
public function execute() {
$this->markDeprecated();
- $this->printText( var_export( $this->getResultData(), true ) );
+ $data = $this->getResult()->getResultData( null, array(
+ 'BC' => array(),
+ 'Types' => array(),
+ 'Strip' => 'all',
+ ) );
+ $this->printText( var_export( $data, true ) );
}
- public function getDescription() {
- return 'DEPRECATED! Output data in PHP\'s var_export() format' . parent::getDescription();
+ public function isDeprecated() {
+ return true;
}
}
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
index d4c7cab4..f34e1ae4 100644
--- a/includes/api/ApiFormatDump.php
+++ b/includes/api/ApiFormatDump.php
@@ -40,14 +40,19 @@ class ApiFormatDump extends ApiFormatBase {
public function execute() {
$this->markDeprecated();
+ $data = $this->getResult()->getResultData( null, array(
+ 'BC' => array(),
+ 'Types' => array(),
+ 'Strip' => 'all',
+ ) );
ob_start();
- var_dump( $this->getResultData() );
+ var_dump( $data );
$result = ob_get_contents();
ob_end_clean();
$this->printText( $result );
}
- public function getDescription() {
- return 'DEPRECATED! Output data in PHP\'s var_dump() format' . parent::getDescription();
+ public function isDeprecated() {
+ return true;
}
}
diff --git a/includes/api/ApiFormatFeedWrapper.php b/includes/api/ApiFormatFeedWrapper.php
index 92600067..00747eef 100644
--- a/includes/api/ApiFormatFeedWrapper.php
+++ b/includes/api/ApiFormatFeedWrapper.php
@@ -46,8 +46,8 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
// 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 );
+ $result->addValue( null, '_feed', $feed, ApiResult::NO_VALIDATE );
+ $result->addValue( null, '_feeditems', $feedItems, ApiResult::NO_VALIDATE );
}
/**
@@ -82,17 +82,41 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
* $result['_feed'] - an instance of one of the $wgFeedClasses classes
* $result['_feeditems'] - an array of FeedItem instances
*/
+ public function initPrinter( $unused = false ) {
+ parent::initPrinter( $unused );
+
+ if ( $this->isDisabled() ) {
+ return;
+ }
+
+ $data = $this->getResult()->getResultData();
+ if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
+ $data['_feed']->httpHeaders();
+ } else {
+ // Error has occurred, print something useful
+ ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' );
+ }
+ }
+
+ /**
+ * 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();
+ $data = $this->getResult()->getResultData();
if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) {
$feed = $data['_feed'];
$items = $data['_feeditems'];
+ // execute() needs to pass strings to $this->printText, not produce output itself.
+ ob_start();
$feed->outHeader();
foreach ( $items as & $item ) {
$feed->outItem( $item );
}
$feed->outFooter();
+ $this->printText( ob_get_clean() );
} 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 d9f9d46a..43877b78 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -30,39 +30,72 @@
*/
class ApiFormatJson extends ApiFormatBase {
- private $mIsRaw;
+ private $isRaw;
public function __construct( ApiMain $main, $format ) {
parent::__construct( $main, $format );
- $this->mIsRaw = ( $format === 'rawfm' );
+ $this->isRaw = ( $format === 'rawfm' );
}
public function getMimeType() {
$params = $this->extractRequestParams();
// callback:
- if ( $params['callback'] ) {
+ if ( isset( $params['callback'] ) ) {
return 'text/javascript';
}
return 'application/json';
}
+ /**
+ * @deprecated since 1.25
+ */
public function getNeedsRawData() {
- return $this->mIsRaw;
+ return $this->isRaw;
}
+ /**
+ * @deprecated since 1.25
+ */
public function getWantsHelp() {
+ wfDeprecated( __METHOD__, '1.25' );
// Help is always ugly in JSON
return false;
}
public function execute() {
$params = $this->extractRequestParams();
- $json = FormatJson::encode(
- $this->getResultData(),
- $this->getIsHtml(),
- $params['utf8'] ? FormatJson::ALL_OK : FormatJson::XMLMETA_OK
- );
+
+ $opt = 0;
+ if ( $this->isRaw ) {
+ $opt |= FormatJson::ALL_OK;
+ $transform = array();
+ } else {
+ switch ( $params['formatversion'] ) {
+ case 1:
+ $opt |= $params['utf8'] ? FormatJson::ALL_OK : FormatJson::XMLMETA_OK;
+ $transform = array(
+ 'BC' => array(),
+ 'Types' => array( 'AssocAsObject' => true ),
+ 'Strip' => 'all',
+ );
+ break;
+
+ case 2:
+ case 'latest':
+ $opt |= $params['ascii'] ? FormatJson::XMLMETA_OK : FormatJson::ALL_OK;
+ $transform = array(
+ 'Types' => array( 'AssocAsObject' => true ),
+ 'Strip' => 'all',
+ );
+ break;
+
+ default:
+ $this->dieUsage( __METHOD__ . ': Unknown value for \'formatversion\'', 'unknownformatversion' );
+ }
+ }
+ $data = $this->getResult()->getResultData( null, $transform );
+ $json = FormatJson::encode( $data, $this->getIsHtml(), $opt );
// Bug 66776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
// Flash, but what it does isn't friendly for the API, so we need to
@@ -73,9 +106,8 @@ class ApiFormatJson extends ApiFormatBase {
);
}
- $callback = $params['callback'];
- if ( $callback !== null ) {
- $callback = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $callback );
+ if ( isset( $params['callback'] ) ) {
+ $callback = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $params['callback'] );
# Prepend a comment to try to avoid attacks against content
# sniffers, such as bug 68187.
$this->printText( "/**/$callback($json)" );
@@ -85,26 +117,28 @@ class ApiFormatJson extends ApiFormatBase {
}
public function getAllowedParams() {
- return array(
- 'callback' => null,
- 'utf8' => false,
- );
- }
-
- 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.',
- );
- }
-
- public function getDescription() {
- if ( $this->mIsRaw ) {
- return 'Output data with the debugging elements in JSON format' . parent::getDescription();
+ if ( $this->isRaw ) {
+ return array();
}
- return 'Output data in JSON format' . parent::getDescription();
+ $ret = array(
+ 'callback' => array(
+ ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-callback',
+ ),
+ 'utf8' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-utf8',
+ ),
+ 'ascii' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-ascii',
+ ),
+ 'formatversion' => array(
+ ApiBase::PARAM_TYPE => array( 1, 2, 'latest' ),
+ ApiBase::PARAM_DFLT => 1,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-json-param-formatversion',
+ ),
+ );
+ return $ret;
}
}
diff --git a/includes/api/ApiFormatNone.php b/includes/api/ApiFormatNone.php
index 78023af3..dc623ac1 100644
--- a/includes/api/ApiFormatNone.php
+++ b/includes/api/ApiFormatNone.php
@@ -36,8 +36,4 @@ class ApiFormatNone extends ApiFormatBase {
public function execute() {
}
-
- public function getDescription() {
- return 'Output nothing' . parent::getDescription();
- }
}
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index 73ce80ef..d88dd40b 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -35,7 +35,29 @@ class ApiFormatPhp extends ApiFormatBase {
}
public function execute() {
- $text = serialize( $this->getResultData() );
+ $params = $this->extractRequestParams();
+
+ switch ( $params['formatversion'] ) {
+ case 1:
+ $transforms = array(
+ 'BC' => array(),
+ 'Types' => array(),
+ 'Strip' => 'all',
+ );
+ break;
+
+ case 2:
+ case 'latest':
+ $transforms = array(
+ 'Types' => array(),
+ 'Strip' => 'all',
+ );
+ break;
+
+ default:
+ $this->dieUsage( __METHOD__ . ': Unknown value for \'formatversion\'', 'unknownformatversion' );
+ }
+ $text = serialize( $this->getResult()->getResultData( null, $transforms ) );
// 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
@@ -54,7 +76,14 @@ class ApiFormatPhp extends ApiFormatBase {
$this->printText( $text );
}
- public function getDescription() {
- return 'Output data in serialized PHP format' . parent::getDescription();
+ public function getAllowedParams() {
+ $ret = array(
+ 'formatversion' => array(
+ ApiBase::PARAM_TYPE => array( 1, 2, 'latest' ),
+ ApiBase::PARAM_DFLT => 1,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-php-param-formatversion',
+ ),
+ );
+ return $ret;
}
}
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index 3f5c8b73..7bb2453d 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -30,20 +30,22 @@
*/
class ApiFormatRaw extends ApiFormatBase {
+ private $errorFallback;
+
/**
* @param ApiMain $main
* @param ApiFormatBase $errorFallback Object to fall back on for errors
*/
public function __construct( ApiMain $main, ApiFormatBase $errorFallback ) {
parent::__construct( $main, 'raw' );
- $this->mErrorFallback = $errorFallback;
+ $this->errorFallback = $errorFallback;
}
public function getMimeType() {
- $data = $this->getResultData();
+ $data = $this->getResult()->getResultData();
if ( isset( $data['error'] ) ) {
- return $this->mErrorFallback->getMimeType();
+ return $this->errorFallback->getMimeType();
}
if ( !isset( $data['mime'] ) ) {
@@ -53,11 +55,28 @@ class ApiFormatRaw extends ApiFormatBase {
return $data['mime'];
}
- public function execute() {
- $data = $this->getResultData();
+ public function initPrinter( $unused = false ) {
+ $data = $this->getResult()->getResultData();
+ if ( isset( $data['error'] ) ) {
+ $this->errorFallback->initPrinter( $unused );
+ } else {
+ parent::initPrinter( $unused );
+ }
+ }
+
+ public function closePrinter() {
+ $data = $this->getResult()->getResultData();
if ( isset( $data['error'] ) ) {
- $this->mErrorFallback->execute();
+ $this->errorFallback->closePrinter();
+ } else {
+ parent::closePrinter();
+ }
+ }
+ public function execute() {
+ $data = $this->getResult()->getResultData();
+ if ( isset( $data['error'] ) ) {
+ $this->errorFallback->execute();
return;
}
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index c451ed77..e739d5a4 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -40,10 +40,15 @@ class ApiFormatTxt extends ApiFormatBase {
public function execute() {
$this->markDeprecated();
- $this->printText( print_r( $this->getResultData(), true ) );
+ $data = $this->getResult()->getResultData( null, array(
+ 'BC' => array(),
+ 'Types' => array(),
+ 'Strip' => 'all',
+ ) );
+ $this->printText( print_r( $data, true ) );
}
- public function getDescription() {
- return 'DEPRECATED! Output data in PHP\'s print_r() format' . parent::getDescription();
+ public function isDeprecated() {
+ return true;
}
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index ec3dc2d9..c18353fe 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -38,18 +38,30 @@ class ApiFormatWddx extends ApiFormatBase {
public function execute() {
$this->markDeprecated();
+ $data = $this->getResult()->getResultData( null, array(
+ 'BC' => array(),
+ 'Types' => array( 'AssocAsObject' => true ),
+ 'Strip' => 'all',
+ ) );
+
if ( !$this->getIsHtml() && !static::useSlowPrinter() ) {
- $this->printText( wddx_serialize_value( $this->getResultData() ) );
+ $txt = wddx_serialize_value( $data );
+ $txt = str_replace(
+ '<struct><var name=\'php_class_name\'><string>stdClass</string></var>',
+ '<struct>',
+ $txt
+ );
+ $this->printText( $txt );
} else {
// Don't do newlines and indentation if we weren't asked
// for pretty output
$nl = ( $this->getIsHtml() ? "\n" : '' );
- $indstr = ' ';
+ $indstr = ( $this->getIsHtml() ? ' ' : '' );
$this->printText( "<?xml version=\"1.0\"?>$nl" );
$this->printText( "<wddxPacket version=\"1.0\">$nl" );
- $this->printText( "$indstr<header/>$nl" );
+ $this->printText( "$indstr<header />$nl" );
$this->printText( "$indstr<data>$nl" );
- $this->slowWddxPrinter( $this->getResultData(), 4 );
+ $this->slowWddxPrinter( $data, 4 );
$this->printText( "$indstr</data>$nl" );
$this->printText( "</wddxPacket>$nl" );
}
@@ -102,44 +114,49 @@ class ApiFormatWddx extends ApiFormatBase {
$indstr = ( $this->getIsHtml() ? str_repeat( ' ', $indent ) : '' );
$indstr2 = ( $this->getIsHtml() ? str_repeat( ' ', $indent + 2 ) : '' );
$nl = ( $this->getIsHtml() ? "\n" : '' );
+
if ( is_array( $elemValue ) ) {
- // Check whether we've got an associative array (<struct>)
- // or a regular array (<array>)
$cnt = count( $elemValue );
- if ( $cnt == 0 || array_keys( $elemValue ) === range( 0, $cnt - 1 ) ) {
- // Regular array
- $this->printText( $indstr . Xml::element( 'array', array(
- 'length' => $cnt ), null ) . $nl );
- foreach ( $elemValue as $subElemValue ) {
- $this->slowWddxPrinter( $subElemValue, $indent + 2 );
- }
- $this->printText( "$indstr</array>$nl" );
- } else {
- // Associative array (<struct>)
- $this->printText( "$indstr<struct>$nl" );
- foreach ( $elemValue as $subElemName => $subElemValue ) {
- $this->printText( $indstr2 . Xml::element( 'var', array(
- 'name' => $subElemName
- ), null ) . $nl );
- $this->slowWddxPrinter( $subElemValue, $indent + 4 );
- $this->printText( "$indstr2</var>$nl" );
- }
- $this->printText( "$indstr</struct>$nl" );
+ if ( $cnt != 0 && array_keys( $elemValue ) !== range( 0, $cnt - 1 ) ) {
+ $elemValue = (object)$elemValue;
+ }
+ }
+
+ if ( is_array( $elemValue ) ) {
+ // Regular array
+ $this->printText( $indstr . Xml::element( 'array', array(
+ 'length' => count( $elemValue ) ), null ) . $nl );
+ foreach ( $elemValue as $subElemValue ) {
+ $this->slowWddxPrinter( $subElemValue, $indent + 2 );
+ }
+ $this->printText( "$indstr</array>$nl" );
+ } elseif ( is_object( $elemValue ) ) {
+ // Associative array (<struct>)
+ $this->printText( "$indstr<struct>$nl" );
+ foreach ( $elemValue as $subElemName => $subElemValue ) {
+ $this->printText( $indstr2 . Xml::element( 'var', array(
+ 'name' => $subElemName
+ ), null ) . $nl );
+ $this->slowWddxPrinter( $subElemValue, $indent + 4 );
+ $this->printText( "$indstr2</var>$nl" );
}
+ $this->printText( "$indstr</struct>$nl" );
} elseif ( is_int( $elemValue ) || is_float( $elemValue ) ) {
$this->printText( $indstr . Xml::element( 'number', null, $elemValue ) . $nl );
} elseif ( is_string( $elemValue ) ) {
- $this->printText( $indstr . Xml::element( 'string', null, $elemValue ) . $nl );
+ $this->printText( $indstr . Xml::element( 'string', null, $elemValue, false ) . $nl );
} elseif ( is_bool( $elemValue ) ) {
$this->printText( $indstr . Xml::element( 'boolean',
array( 'value' => $elemValue ? 'true' : 'false' ) ) . $nl
);
+ } elseif ( $elemValue === null ) {
+ $this->printText( $indstr . Xml::element( 'null', array() ) . $nl );
} else {
ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) );
}
}
- public function getDescription() {
- return 'DEPRECATED! Output data in WDDX format' . parent::getDescription();
+ public function isDeprecated() {
+ return true;
}
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index b3d59379..fa0bac34 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -39,6 +39,9 @@ class ApiFormatXml extends ApiFormatBase {
return 'text/xml';
}
+ /**
+ * @deprecated since 1.25
+ */
public function getNeedsRawData() {
return true;
}
@@ -56,18 +59,32 @@ class ApiFormatXml extends ApiFormatBase {
if ( !is_null( $this->mXslt ) ) {
$this->addXslt();
}
- if ( $this->mIncludeNamespace ) {
+
+ $result = $this->getResult();
+ if ( $this->mIncludeNamespace && $result->getResultData( 'xmlns' ) === null ) {
// If the result data already contains an 'xmlns' namespace added
// for custom XML output types, it will override the one for the
// generic API results.
// This allows API output of other XML types like Atom, RSS, RSD.
- $data = $this->getResultData() + array( 'xmlns' => self::$namespace );
- } else {
- $data = $this->getResultData();
+ $result->addValue( null, 'xmlns', self::$namespace, ApiResult::NO_SIZE_CHECK );
}
+ $data = $result->getResultData( null, array(
+ 'Custom' => function ( &$data, &$metadata ) {
+ if ( isset( $metadata[ApiResult::META_TYPE] ) ) {
+ // We want to use non-BC for BCassoc to force outputting of _idx.
+ switch( $metadata[ApiResult::META_TYPE] ) {
+ case 'BCassoc':
+ $metadata[ApiResult::META_TYPE] = 'assoc';
+ break;
+ }
+ }
+ },
+ 'BC' => array( 'nobool', 'no*', 'nosub' ),
+ 'Types' => array( 'ArmorKVP' => '_name' ),
+ ) );
$this->printText(
- self::recXmlPrint( $this->mRootElemName,
+ static::recXmlPrint( $this->mRootElemName,
$data,
$this->getIsHtml() ? -2 : null
)
@@ -77,143 +94,185 @@ class ApiFormatXml extends ApiFormatBase {
/**
* This method takes an array and converts it to XML.
*
- * There are several noteworthy cases:
- *
- * If array contains a key '_element', then the code assumes that ALL
- * other keys are not important and replaces them with the
- * value['_element'].
- *
- * @par Example:
- * @verbatim
- * name='root', value = array( '_element'=>'page', 'x', 'y', 'z')
- * @endverbatim
- * creates:
- * @verbatim
- * <root> <page>x</page> <page>y</page> <page>z</page> </root>
- * @endverbatim
- *
- * If any of the array's element key is '*', then the code treats all
- * other key->value pairs as attributes, and the value['*'] as the
- * element's content.
- *
- * @par Example:
- * @verbatim
- * name='root', value = array( '*'=>'text', 'lang'=>'en', 'id'=>10)
- * @endverbatim
- * creates:
- * @verbatim
- * <root lang='en' id='10'>text</root>
- * @endverbatim
- *
- * Finally neither key is found, all keys become element names, and values
- * become element content.
- *
- * @note The method is recursive, so the same rules apply to any
- * sub-arrays.
- *
- * @param string $elemName
- * @param mixed $elemValue
- * @param int $indent
- *
+ * @param string|null $name Tag name
+ * @param mixed $value Tag value (attributes/content/subelements)
+ * @param int|null $indent Indentation
+ * @param array $attributes Additional attributes
* @return string
*/
- public static function recXmlPrint( $elemName, $elemValue, $indent ) {
+ public static function recXmlPrint( $name, $value, $indent, $attributes = array() ) {
$retval = '';
- if ( !is_null( $indent ) ) {
- $indent += 2;
+ if ( $indent !== null ) {
+ if ( $name !== null ) {
+ $indent += 2;
+ }
$indstr = "\n" . str_repeat( ' ', $indent );
} else {
$indstr = '';
}
- $elemName = str_replace( ' ', '_', $elemName );
-
- if ( is_array( $elemValue ) ) {
- if ( isset( $elemValue['*'] ) ) {
- $subElemContent = $elemValue['*'];
- unset( $elemValue['*'] );
- // Add xml:space="preserve" to the
- // element so XML parsers will leave
- // whitespace in the content alone
- $elemValue['xml:space'] = 'preserve';
- } else {
- $subElemContent = null;
+ if ( is_object( $value ) ) {
+ $value = (array)$value;
+ }
+ if ( is_array( $value ) ) {
+ $contentKey = isset( $value[ApiResult::META_CONTENT] )
+ ? $value[ApiResult::META_CONTENT]
+ : '*';
+ $subelementKeys = isset( $value[ApiResult::META_SUBELEMENTS] )
+ ? $value[ApiResult::META_SUBELEMENTS]
+ : array();
+ if ( isset( $value[ApiResult::META_BC_SUBELEMENTS] ) ) {
+ $subelementKeys = array_merge(
+ $subelementKeys, $value[ApiResult::META_BC_SUBELEMENTS]
+ );
}
+ $preserveKeys = isset( $value[ApiResult::META_PRESERVE_KEYS] )
+ ? $value[ApiResult::META_PRESERVE_KEYS]
+ : array();
+ $indexedTagName = isset( $value[ApiResult::META_INDEXED_TAG_NAME] )
+ ? self::mangleName( $value[ApiResult::META_INDEXED_TAG_NAME], $preserveKeys )
+ : '_v';
+ $bcBools = isset( $value[ApiResult::META_BC_BOOLS] )
+ ? $value[ApiResult::META_BC_BOOLS]
+ : array();
+ $indexSubelements = isset( $value[ApiResult::META_TYPE] )
+ ? $value[ApiResult::META_TYPE] !== 'array'
+ : false;
- if ( isset( $elemValue['_element'] ) ) {
- $subElemIndName = $elemValue['_element'];
- unset( $elemValue['_element'] );
- } else {
- $subElemIndName = null;
- }
+ $content = null;
+ $subelements = array();
+ $indexedSubelements = array();
+ foreach ( $value as $k => $v ) {
+ if ( ApiResult::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
+ continue;
+ }
- if ( isset( $elemValue['_subelements'] ) ) {
- foreach ( $elemValue['_subelements'] as $subElemId ) {
- if ( isset( $elemValue[$subElemId] ) && !is_array( $elemValue[$subElemId] ) ) {
- $elemValue[$subElemId] = array( '*' => $elemValue[$subElemId] );
- }
+ $oldv = $v;
+ if ( is_bool( $v ) && !in_array( $k, $bcBools, true ) ) {
+ $v = $v ? 'true' : 'false';
}
- unset( $elemValue['_subelements'] );
- }
- $indElements = array();
- $subElements = array();
- foreach ( $elemValue as $subElemId => & $subElemValue ) {
- if ( is_int( $subElemId ) ) {
- $indElements[] = $subElemValue;
- unset( $elemValue[$subElemId] );
- } 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 ( $name !== null && $k === $contentKey ) {
+ $content = $v;
+ } elseif ( is_int( $k ) ) {
+ $indexedSubelements[$k] = $v;
+ } elseif ( is_array( $v ) || is_object( $v ) ) {
+ $subelements[self::mangleName( $k, $preserveKeys )] = $v;
+ } elseif ( in_array( $k, $subelementKeys, true ) || $name === null ) {
+ $subelements[self::mangleName( $k, $preserveKeys )] = array(
+ 'content' => $v,
+ ApiResult::META_CONTENT => 'content',
+ ApiResult::META_TYPE => 'assoc',
+ );
+ } elseif ( is_bool( $oldv ) ) {
+ if ( $oldv ) {
+ $attributes[self::mangleName( $k, $preserveKeys )] = '';
}
+ } elseif ( $v !== null ) {
+ $attributes[self::mangleName( $k, $preserveKeys )] = $v;
}
}
- if ( is_null( $subElemIndName ) && count( $indElements ) ) {
- ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys " .
- "without _element value. Use ApiResult::setIndexedTagName()." );
- }
-
- if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) {
- ApiBase::dieDebug( __METHOD__, "($elemName, ...) has content and subelements" );
+ if ( $content !== null ) {
+ if ( $subelements || $indexedSubelements ) {
+ $subelements[self::mangleName( $contentKey, $preserveKeys )] = array(
+ 'content' => $content,
+ ApiResult::META_CONTENT => 'content',
+ ApiResult::META_TYPE => 'assoc',
+ );
+ $content = null;
+ } elseif ( is_scalar( $content ) ) {
+ // Add xml:space="preserve" to the element so XML parsers
+ // will leave whitespace in the content alone
+ $attributes += array( 'xml:space' => 'preserve' );
+ }
}
- if ( !is_null( $subElemContent ) ) {
- $retval .= $indstr . Xml::element( $elemName, $elemValue, $subElemContent );
- } elseif ( !count( $indElements ) && !count( $subElements ) ) {
- $retval .= $indstr . Xml::element( $elemName, $elemValue );
+ if ( $content !== null ) {
+ if ( is_scalar( $content ) ) {
+ $retval .= $indstr . Xml::element( $name, $attributes, $content );
+ } else {
+ if ( $name !== null ) {
+ $retval .= $indstr . Xml::element( $name, $attributes, null );
+ }
+ $retval .= static::recXmlPrint( null, $content, $indent );
+ if ( $name !== null ) {
+ $retval .= $indstr . Xml::closeElement( $name );
+ }
+ }
+ } elseif ( !$indexedSubelements && !$subelements ) {
+ if ( $name !== null ) {
+ $retval .= $indstr . Xml::element( $name, $attributes );
+ }
} else {
- $retval .= $indstr . Xml::element( $elemName, $elemValue, null );
-
- foreach ( $subElements as $subElemId => & $subElemValue ) {
- $retval .= self::recXmlPrint( $subElemId, $subElemValue, $indent );
+ if ( $name !== null ) {
+ $retval .= $indstr . Xml::element( $name, $attributes, null );
}
-
- foreach ( $indElements as &$subElemValue ) {
- $retval .= self::recXmlPrint( $subElemIndName, $subElemValue, $indent );
+ foreach ( $subelements as $k => $v ) {
+ $retval .= static::recXmlPrint( $k, $v, $indent );
+ }
+ foreach ( $indexedSubelements as $k => $v ) {
+ $retval .= static::recXmlPrint( $indexedTagName, $v, $indent,
+ $indexSubelements ? array( '_idx' => $k ) : array()
+ );
+ }
+ if ( $name !== null ) {
+ $retval .= $indstr . Xml::closeElement( $name );
}
-
- $retval .= $indstr . Xml::closeElement( $elemName );
}
- } elseif ( !is_object( $elemValue ) ) {
+ } else {
// to make sure null value doesn't produce unclosed element,
- // which is what Xml::element( $elemName, null, null ) returns
- if ( $elemValue === null ) {
- $retval .= $indstr . Xml::element( $elemName );
+ // which is what Xml::element( $name, null, null ) returns
+ if ( $value === null ) {
+ $retval .= $indstr . Xml::element( $name, $attributes );
} else {
- $retval .= $indstr . Xml::element( $elemName, null, $elemValue );
+ $retval .= $indstr . Xml::element( $name, $attributes, $value );
}
}
return $retval;
}
+ /**
+ * Mangle XML-invalid names to be valid in XML
+ * @param string $name
+ * @param array $preserveKeys Names to not mangle
+ * @return string Mangled name
+ */
+ private static function mangleName( $name, $preserveKeys = array() ) {
+ static $nsc = null, $nc = null;
+
+ if ( in_array( $name, $preserveKeys, true ) ) {
+ return $name;
+ }
+
+ if ( $name === '' ) {
+ return '_';
+ }
+
+ if ( $nsc === null ) {
+ // Note we omit ':' from $nsc and $nc because it's reserved for XML
+ // namespacing, and we omit '_' from $nsc (but not $nc) because we
+ // reserve it.
+ $nsc = 'A-Za-z\x{C0}-\x{D6}\x{D8}-\x{F6}\x{F8}-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}' .
+ '\x{200C}-\x{200D}\x{2070}-\x{218F}\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}' .
+ '\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}';
+ $nc = $nsc . '_\-.0-9\x{B7}\x{300}-\x{36F}\x{203F}-\x{2040}';
+ }
+
+ if ( preg_match( "/^[$nsc][$nc]*$/uS", $name ) ) {
+ return $name;
+ }
+
+ return '_' . preg_replace_callback(
+ "/[^$nc]/uS",
+ function ( $m ) {
+ return sprintf( '.%X.', utf8ToCodepoint( $m[0] ) );
+ },
+ str_replace( '.', '.2E.', $name )
+ );
+ }
+
function addXslt() {
$nt = Title::newFromText( $this->mXslt );
if ( is_null( $nt ) || !$nt->exists() ) {
@@ -237,20 +296,13 @@ class ApiFormatXml extends ApiFormatBase {
public function getAllowedParams() {
return array(
- 'xslt' => null,
- 'includexmlnamespace' => false,
+ 'xslt' => array(
+ ApiBase::PARAM_HELP_MSG => 'apihelp-xml-param-xslt',
+ ),
+ 'includexmlnamespace' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-xml-param-includexmlnamespace',
+ ),
);
}
-
- public function getParamDescription() {
- return array(
- 'xslt' => 'If specified, adds <xslt> as stylesheet. This should be a wiki page '
- . 'in the MediaWiki namespace whose page name ends with ".xsl"',
- 'includexmlnamespace' => 'If specified, adds an XML namespace'
- );
- }
-
- public function getDescription() {
- return 'Output data in XML format' . parent::getDescription();
- }
}
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index 3798f894..c9089a7d 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -40,7 +40,7 @@ class ApiFormatYaml extends ApiFormatJson {
parent::execute();
}
- public function getDescription() {
- return 'DEPRECATED! Output data in YAML format' . ApiFormatBase::getDescription();
+ public function isDeprecated() {
+ return true;
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index bcd6c12e..53e8c343 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -2,9 +2,9 @@
/**
*
*
- * Created on Sep 6, 2006
+ * Created on Aug 29, 2014
*
- * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ * 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
@@ -25,102 +25,596 @@
*/
/**
- * This is a simple class to handle action=help
+ * Class to output help for an API module
*
+ * @since 1.25 completely rewritten
* @ingroup API
*/
class ApiHelp extends ApiBase {
- /**
- * Module for displaying help
- */
public function execute() {
- // Get parameters
$params = $this->extractRequestParams();
+ $modules = array();
- if ( !isset( $params['modules'] ) && !isset( $params['querymodules'] ) ) {
- $this->dieUsage( '', 'help' );
+ foreach ( $params['modules'] as $path ) {
+ $modules[] = $this->getModuleFromPath( $path );
}
- $this->getMain()->setHelp();
- $result = $this->getResult();
+ // Get the help
+ $context = new DerivativeContext( $this->getMain()->getContext() );
+ $context->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'apioutput' ) );
+ $context->setLanguage( $this->getMain()->getLanguage() );
+ $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
+ $out = new OutputPage( $context );
+ $context->setOutput( $out );
+
+ self::getHelp( $context, $modules, $params );
- if ( is_array( $params['modules'] ) ) {
- $modules = $params['modules'];
+ // Grab the output from the skin
+ ob_start();
+ $context->getOutput()->output();
+ $html = ob_get_clean();
+
+ $result = $this->getResult();
+ if ( $params['wrap'] ) {
+ $data = array(
+ 'mime' => 'text/html',
+ 'help' => $html,
+ );
+ ApiResult::setSubelementsList( $data, 'help' );
+ $result->addValue( null, $this->getModuleName(), $data );
} else {
- $modules = array();
+ $result->reset();
+ $result->addValue( null, 'text', $html, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, 'mime', 'text/html', ApiResult::NO_SIZE_CHECK );
+ }
+ }
+
+ /**
+ * Generate help for the specified modules
+ *
+ * Help is placed into the OutputPage object returned by
+ * $context->getOutput().
+ *
+ * Recognized options include:
+ * - headerlevel: (int) Header tag level
+ * - nolead: (bool) Skip the inclusion of api-help-lead
+ * - noheader: (bool) Skip the inclusion of the top-level section headers
+ * - submodules: (bool) Include help for submodules of the current module
+ * - recursivesubmodules: (bool) Include help for submodules recursively
+ * - helptitle: (string) Title to link for additional modules' help. Should contain $1.
+ *
+ * @param IContextSource $context
+ * @param ApiBase[]|ApiBase $modules
+ * @param array $options Formatting options (described above)
+ * @return string
+ */
+ public static function getHelp( IContextSource $context, $modules, array $options ) {
+ global $wgMemc, $wgContLang;
+
+ if ( !is_array( $modules ) ) {
+ $modules = array( $modules );
}
- if ( is_array( $params['querymodules'] ) ) {
- $this->logFeatureUsage( 'action=help&querymodules' );
- $queryModules = $params['querymodules'];
- foreach ( $queryModules as $m ) {
- $modules[] = 'query+' . $m;
+ $out = $context->getOutput();
+ $out->addModules( 'mediawiki.apihelp' );
+ $out->setPageTitle( $context->msg( 'api-help-title' ) );
+
+ $cacheKey = null;
+ if ( count( $modules ) == 1 && $modules[0] instanceof ApiMain &&
+ $options['recursivesubmodules'] && $context->getLanguage() === $wgContLang
+ ) {
+ $cacheHelpTimeout = $context->getConfig()->get( 'APICacheHelpTimeout' );
+ if ( $cacheHelpTimeout > 0 ) {
+ // Get help text from cache if present
+ $cacheKey = wfMemcKey( 'apihelp', $modules[0]->getModulePath(),
+ str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) );
+ $cached = $wgMemc->get( $cacheKey );
+ if ( $cached ) {
+ $out->addHTML( $cached );
+ return;
+ }
}
- } else {
- $queryModules = array();
}
+ if ( $out->getHTML() !== '' ) {
+ // Don't save to cache, there's someone else's content in the page
+ // already
+ $cacheKey = null;
+ }
+
+ $options['recursivesubmodules'] = !empty( $options['recursivesubmodules'] );
+ $options['submodules'] = $options['recursivesubmodules'] || !empty( $options['submodules'] );
- $r = array();
- foreach ( $modules as $m ) {
- // sub-modules could be given in the form of "name[+name[+name...]]"
- $subNames = explode( '+', $m );
- if ( count( $subNames ) === 1 ) {
- // In case the '+' was typed into URL, it resolves as a space
- $subNames = explode( ' ', $m );
+ // Prepend lead
+ if ( empty( $options['nolead'] ) ) {
+ $msg = $context->msg( 'api-help-lead' );
+ if ( !$msg->isDisabled() ) {
+ $out->addHTML( $msg->parseAsBlock() );
}
+ }
- $module = $this->getMain();
- $subNamesCount = count( $subNames );
- for ( $i = 0; $i < $subNamesCount; $i++ ) {
- $subs = $module->getModuleManager();
- if ( $subs === null ) {
- $module = null;
- } else {
- $module = $subs->getModule( $subNames[$i] );
- }
+ $haveModules = array();
+ $out->addHTML( self::getHelpInternal( $context, $modules, $options, $haveModules ) );
- if ( $module === null ) {
- if ( count( $subNames ) === 2
- && $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.
- $name = $subNames[1];
- } else {
- $name = implode( '+', array_slice( $subNames, 0, $i + 1 ) );
- }
- $r[] = array( 'name' => $name, 'missing' => '' );
- break;
+ $helptitle = isset( $options['helptitle'] ) ? $options['helptitle'] : null;
+ $html = self::fixHelpLinks( $out->getHTML(), $helptitle, $haveModules );
+ $out->clearHTML();
+ $out->addHTML( $html );
+
+ if ( $cacheKey !== null ) {
+ $wgMemc->set( $cacheKey, $out->getHTML(), $cacheHelpTimeout );
+ }
+ }
+
+ /**
+ * Replace Special:ApiHelp links with links to api.php
+ *
+ * @param string $html
+ * @param string|null $helptitle Title to link to rather than api.php, must contain '$1'
+ * @param array $localModules Modules to link within the current page
+ * @return string
+ */
+ public static function fixHelpLinks( $html, $helptitle = null, $localModules = array() ) {
+ $formatter = new HtmlFormatter( $html );
+ $doc = $formatter->getDoc();
+ $xpath = new DOMXPath( $doc );
+ $nodes = $xpath->query( '//a[@href][not(contains(@class,\'apihelp-linktrail\'))]' );
+ foreach ( $nodes as $node ) {
+ $href = $node->getAttribute( 'href' );
+ do {
+ $old = $href;
+ $href = rawurldecode( $href );
+ } while ( $old !== $href );
+ if ( preg_match( '!Special:ApiHelp/([^&/|]+)!', $href, $m ) ) {
+ if ( isset( $localModules[$m[1]] ) ) {
+ $href = '#' . $m[1];
+ } elseif ( $helptitle !== null ) {
+ $href = Title::newFromText( str_replace( '$1', $m[1], $helptitle ) )
+ ->getFullUrl();
} else {
- $type = $subs->getModuleGroup( $subNames[$i] );
+ $href = wfAppendQuery( wfScript( 'api' ), array(
+ 'action' => 'help',
+ 'modules' => $m[1],
+ ) );
}
- }
-
- if ( $module !== null ) {
- $r[] = $this->buildModuleHelp( $module, $type );
+ $node->setAttribute( 'href', $href );
+ $node->removeAttribute( 'title' );
}
}
- $result->setIndexedTagName( $r, 'module' );
- $result->addValue( null, $this->getModuleName(), $r );
+ return $formatter->getText();
}
/**
- * @param ApiBase $module
- * @param string $type What type of request is this? e.g. action, query, list, prop, meta, format
+ * Wrap a message in HTML with a class.
+ *
+ * @param Message $msg
+ * @param string $class
+ * @param string $tag
* @return string
*/
- private function buildModuleHelp( $module, $type ) {
- $msg = ApiMain::makeHelpMsgHeader( $module, $type );
+ private static function wrap( Message $msg, $class, $tag = 'span' ) {
+ return Html::rawElement( $tag, array( 'class' => $class ),
+ $msg->parse()
+ );
+ }
+
+ /**
+ * Recursively-called function to actually construct the help
+ *
+ * @param IContextSource $context
+ * @param ApiBase[] $modules
+ * @param array $options
+ * @param array &$haveModules
+ * @return string
+ */
+ private static function getHelpInternal( IContextSource $context, array $modules,
+ array $options, &$haveModules
+ ) {
+ $out = '';
+
+ $level = min( 6, empty( $options['headerlevel'] ) ? 2 : $options['headerlevel'] );
+ $options['headerlevel'] = $level;
+
+ foreach ( $modules as $module ) {
+ $haveModules[$module->getModulePath()] = true;
+ $module->setContext( $context );
+ $help = array(
+ 'header' => '',
+ 'flags' => '',
+ 'description' => '',
+ 'help-urls' => '',
+ 'parameters' => '',
+ 'examples' => '',
+ 'submodules' => '',
+ );
+
+ if ( empty( $options['noheader'] ) ) {
+ $path = $module->getModulePath();
+ if ( $module->isMain() ) {
+ $header = $context->msg( 'api-help-main-header' )->parse();
+ } else {
+ $name = $module->getModuleName();
+ $header = $module->getParent()->getModuleManager()->getModuleGroup( $name ) .
+ "=$name";
+ if ( $module->getModulePrefix() !== '' ) {
+ $header .= ' ' .
+ $context->msg( 'parentheses', $module->getModulePrefix() )->parse();
+ }
+ }
+ $help['header'] .= Html::element( "h$level",
+ array( 'id' => $path, 'class' => 'apihelp-header' ),
+ $header
+ );
+ }
+
+ $links = array();
+ $any = false;
+ for ( $m = $module; $m !== null; $m = $m->getParent() ) {
+ $name = $m->getModuleName();
+ if ( $name === 'main_int' ) {
+ $name = 'main';
+ }
- $msg2 = $module->makeHelpMsg();
- if ( $msg2 !== false ) {
- $msg .= $msg2;
+ if ( count( $modules ) === 1 && $m === $modules[0] &&
+ !( !empty( $options['submodules'] ) && $m->getModuleManager() )
+ ) {
+ $link = Html::element( 'b', null, $name );
+ } else {
+ $link = SpecialPage::getTitleFor( 'ApiHelp', $m->getModulePath() )->getLocalURL();
+ $link = Html::element( 'a',
+ array( 'href' => $link, 'class' => 'apihelp-linktrail' ),
+ $name
+ );
+ $any = true;
+ }
+ array_unshift( $links, $link );
+ }
+ if ( $any ) {
+ $help['header'] .= self::wrap(
+ $context->msg( 'parentheses' )
+ ->rawParams( $context->getLanguage()->pipeList( $links ) ),
+ 'apihelp-linktrail', 'div'
+ );
+ }
+
+ $flags = $module->getHelpFlags();
+ if ( $flags ) {
+ $help['flags'] .= Html::openElement( 'div',
+ array( 'class' => 'apihelp-block apihelp-flags' ) );
+ $msg = $context->msg( 'api-help-flags' );
+ if ( !$msg->isDisabled() ) {
+ $help['flags'] .= self::wrap(
+ $msg->numParams( count( $flags ) ), 'apihelp-block-head', 'div'
+ );
+ }
+ $help['flags'] .= Html::openElement( 'ul' );
+ foreach ( $flags as $flag ) {
+ $help['flags'] .= Html::rawElement( 'li', null,
+ self::wrap( $context->msg( "api-help-flag-$flag" ), "apihelp-flag-$flag" )
+ );
+ }
+ $help['flags'] .= Html::closeElement( 'ul' );
+ $help['flags'] .= Html::closeElement( 'div' );
+ }
+
+ foreach ( $module->getFinalDescription() as $msg ) {
+ $msg->setContext( $context );
+ $help['description'] .= $msg->parseAsBlock();
+ }
+
+ $urls = $module->getHelpUrls();
+ if ( $urls ) {
+ $help['help-urls'] .= Html::openElement( 'div',
+ array( 'class' => 'apihelp-block apihelp-help-urls' )
+ );
+ $msg = $context->msg( 'api-help-help-urls' );
+ if ( !$msg->isDisabled() ) {
+ $help['help-urls'] .= self::wrap(
+ $msg->numParams( count( $urls ) ), 'apihelp-block-head', 'div'
+ );
+ }
+ if ( !is_array( $urls ) ) {
+ $urls = array( $urls );
+ }
+ $help['help-urls'] .= Html::openElement( 'ul' );
+ foreach ( $urls as $url ) {
+ $help['help-urls'] .= Html::rawElement( 'li', null,
+ Html::element( 'a', array( 'href' => $url ), $url )
+ );
+ }
+ $help['help-urls'] .= Html::closeElement( 'ul' );
+ $help['help-urls'] .= Html::closeElement( 'div' );
+ }
+
+ $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+ $groups = array();
+ if ( $params ) {
+ $help['parameters'] .= Html::openElement( 'div',
+ array( 'class' => 'apihelp-block apihelp-parameters' )
+ );
+ $msg = $context->msg( 'api-help-parameters' );
+ if ( !$msg->isDisabled() ) {
+ $help['parameters'] .= self::wrap(
+ $msg->numParams( count( $params ) ), 'apihelp-block-head', 'div'
+ );
+ }
+ $help['parameters'] .= Html::openElement( 'dl' );
+
+ $descriptions = $module->getFinalParamDescription();
+
+ foreach ( $params as $name => $settings ) {
+ if ( !is_array( $settings ) ) {
+ $settings = array( ApiBase::PARAM_DFLT => $settings );
+ }
+
+ $help['parameters'] .= Html::element( 'dt', null,
+ $module->encodeParamName( $name ) );
+
+ // Add description
+ $description = array();
+ if ( isset( $descriptions[$name] ) ) {
+ foreach ( $descriptions[$name] as $msg ) {
+ $msg->setContext( $context );
+ $description[] = $msg->parseAsBlock();
+ }
+ }
+
+ // Add usage info
+ $info = array();
+
+ // Required?
+ if ( !empty( $settings[ApiBase::PARAM_REQUIRED] ) ) {
+ $info[] = $context->msg( 'api-help-param-required' )->parse();
+ }
+
+ // Custom info?
+ if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
+ foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
+ $tag = array_shift( $i );
+ $info[] = $context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
+ ->numParams( count( $i ) )
+ ->params( $context->getLanguage()->commaList( $i ) )
+ ->params( $module->getModulePrefix() )
+ ->parse();
+ }
+ }
+
+ // Type documentation
+ if ( !isset( $settings[ApiBase::PARAM_TYPE] ) ) {
+ $dflt = isset( $settings[ApiBase::PARAM_DFLT] )
+ ? $settings[ApiBase::PARAM_DFLT]
+ : null;
+ if ( is_bool( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'boolean';
+ } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'string';
+ } elseif ( is_int( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'integer';
+ }
+ }
+ if ( isset( $settings[ApiBase::PARAM_TYPE] ) ) {
+ $type = $settings[ApiBase::PARAM_TYPE];
+ $multi = !empty( $settings[ApiBase::PARAM_ISMULTI] );
+ $hintPipeSeparated = true;
+ $count = ApiBase::LIMIT_SML2 + 1;
+
+ if ( is_array( $type ) ) {
+ $count = count( $type );
+ $links = isset( $settings[ApiBase::PARAM_VALUE_LINKS] )
+ ? $settings[ApiBase::PARAM_VALUE_LINKS]
+ : array();
+ $type = array_map( function ( $v ) use ( $links ) {
+ $ret = wfEscapeWikiText( $v );
+ if ( isset( $links[$v] ) ) {
+ $ret = "[[{$links[$v]}|$ret]]";
+ }
+ return $ret;
+ }, $type );
+ $i = array_search( '', $type, true );
+ if ( $i === false ) {
+ $type = $context->getLanguage()->commaList( $type );
+ } else {
+ unset( $type[$i] );
+ $type = $context->msg( 'api-help-param-list-can-be-empty' )
+ ->numParams( count( $type ) )
+ ->params( $context->getLanguage()->commaList( $type ) )
+ ->parse();
+ }
+ $info[] = $context->msg( 'api-help-param-list' )
+ ->params( $multi ? 2 : 1 )
+ ->params( $type )
+ ->parse();
+ $hintPipeSeparated = false;
+ } else {
+ switch ( $type ) {
+ case 'submodule':
+ $groups[] = $name;
+ $submodules = $module->getModuleManager()->getNames( $name );
+ $count = count( $submodules );
+ sort( $submodules );
+ $prefix = $module->isMain()
+ ? '' : ( $module->getModulePath() . '+' );
+ $submodules = array_map( function ( $name ) use ( $prefix ) {
+ return "[[Special:ApiHelp/{$prefix}{$name}|{$name}]]";
+ }, $submodules );
+ $info[] = $context->msg( 'api-help-param-list' )
+ ->params( $multi ? 2 : 1 )
+ ->params( $context->getLanguage()->commaList( $submodules ) )
+ ->parse();
+ $hintPipeSeparated = false;
+ break;
+
+ case 'namespace':
+ $namespaces = MWNamespace::getValidNamespaces();
+ $count = count( $namespaces );
+ $info[] = $context->msg( 'api-help-param-list' )
+ ->params( $multi ? 2 : 1 )
+ ->params( $context->getLanguage()->commaList( $namespaces ) )
+ ->parse();
+ $hintPipeSeparated = false;
+ break;
+
+ case 'limit':
+ if ( isset( $settings[ApiBase::PARAM_MAX2] ) ) {
+ $info[] = $context->msg( 'api-help-param-limit2' )
+ ->numParams( $settings[ApiBase::PARAM_MAX] )
+ ->numParams( $settings[ApiBase::PARAM_MAX2] )
+ ->parse();
+ } else {
+ $info[] = $context->msg( 'api-help-param-limit' )
+ ->numParams( $settings[ApiBase::PARAM_MAX] )
+ ->parse();
+ }
+ break;
+
+ case 'integer':
+ // Possible messages:
+ // api-help-param-integer-min,
+ // api-help-param-integer-max,
+ // api-help-param-integer-minmax
+ $suffix = '';
+ $min = $max = 0;
+ if ( isset( $settings[ApiBase::PARAM_MIN] ) ) {
+ $suffix .= 'min';
+ $min = $settings[ApiBase::PARAM_MIN];
+ }
+ if ( isset( $settings[ApiBase::PARAM_MAX] ) ) {
+ $suffix .= 'max';
+ $max = $settings[ApiBase::PARAM_MAX];
+ }
+ if ( $suffix !== '' ) {
+ $info[] =
+ $context->msg( "api-help-param-integer-$suffix" )
+ ->params( $multi ? 2 : 1 )
+ ->numParams( $min, $max )
+ ->parse();
+ }
+ break;
+
+ case 'upload':
+ $info[] = $context->msg( 'api-help-param-upload' )
+ ->parse();
+ break;
+ }
+ }
+
+ if ( $multi ) {
+ $extra = array();
+ if ( $hintPipeSeparated ) {
+ $extra[] = $context->msg( 'api-help-param-multi-separate' )->parse();
+ }
+ if ( $count > ApiBase::LIMIT_SML1 ) {
+ $extra[] = $context->msg( 'api-help-param-multi-max' )
+ ->numParams( ApiBase::LIMIT_SML1, ApiBase::LIMIT_SML2 )
+ ->parse();
+ }
+ if ( $extra ) {
+ $info[] = join( ' ', $extra );
+ }
+ }
+ }
+
+ // Add default
+ $default = isset( $settings[ApiBase::PARAM_DFLT] )
+ ? $settings[ApiBase::PARAM_DFLT]
+ : null;
+ if ( $default === '' ) {
+ $info[] = $context->msg( 'api-help-param-default-empty' )
+ ->parse();
+ } elseif ( $default !== null && $default !== false ) {
+ $info[] = $context->msg( 'api-help-param-default' )
+ ->params( wfEscapeWikiText( $default ) )
+ ->parse();
+ }
+
+ if ( !array_filter( $description ) ) {
+ $description = array( self::wrap(
+ $context->msg( 'api-help-param-no-description' ),
+ 'apihelp-empty'
+ ) );
+ }
+
+ // Add "deprecated" flag
+ if ( !empty( $settings[ApiBase::PARAM_DEPRECATED] ) ) {
+ $help['parameters'] .= Html::openElement( 'dd',
+ array( 'class' => 'info' ) );
+ $help['parameters'] .= self::wrap(
+ $context->msg( 'api-help-param-deprecated' ),
+ 'apihelp-deprecated', 'strong'
+ );
+ $help['parameters'] .= Html::closeElement( 'dd' );
+ }
+
+ if ( $description ) {
+ $description = join( '', $description );
+ $description = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $description );
+ $help['parameters'] .= Html::rawElement( 'dd',
+ array( 'class' => 'description' ), $description );
+ }
+
+ foreach ( $info as $i ) {
+ $help['parameters'] .= Html::rawElement( 'dd', array( 'class' => 'info' ), $i );
+ }
+ }
+
+ $help['parameters'] .= Html::closeElement( 'dl' );
+ $help['parameters'] .= Html::closeElement( 'div' );
+ }
+
+ $examples = $module->getExamplesMessages();
+ if ( $examples ) {
+ $help['examples'] .= Html::openElement( 'div',
+ array( 'class' => 'apihelp-block apihelp-examples' ) );
+ $msg = $context->msg( 'api-help-examples' );
+ if ( !$msg->isDisabled() ) {
+ $help['examples'] .= self::wrap(
+ $msg->numParams( count( $examples ) ), 'apihelp-block-head', 'div'
+ );
+ }
+
+ $help['examples'] .= Html::openElement( 'dl' );
+ foreach ( $examples as $qs => $msg ) {
+ $msg = ApiBase::makeMessage( $msg, $context, array(
+ $module->getModulePrefix(),
+ $module->getModuleName(),
+ $module->getModulePath()
+ ) );
+
+ $link = wfAppendQuery( wfScript( 'api' ), $qs );
+ $help['examples'] .= Html::rawElement( 'dt', null, $msg->parse() );
+ $help['examples'] .= Html::rawElement( 'dd', null,
+ Html::element( 'a', array( 'href' => $link ), "api.php?$qs" )
+ );
+ }
+ $help['examples'] .= Html::closeElement( 'dl' );
+ $help['examples'] .= Html::closeElement( 'div' );
+ }
+
+ if ( $options['submodules'] && $module->getModuleManager() ) {
+ $manager = $module->getModuleManager();
+ $submodules = array();
+ foreach ( $groups as $group ) {
+ $names = $manager->getNames( $group );
+ sort( $names );
+ foreach ( $names as $name ) {
+ $submodules[] = $manager->getModule( $name );
+ }
+ }
+ $help['submodules'] .= self::getHelpInternal( $context, $submodules, array(
+ 'submodules' => $options['recursivesubmodules'],
+ 'headerlevel' => $level + 1,
+ 'noheader' => false,
+ ) + $options, $haveModules );
+ }
+
+ $module->modifyHelp( $help, $options );
+
+ Hooks::run( 'APIHelpModifyOutput', array( $module, &$help, $options ) );
+
+ $out .= join( "\n", $help );
}
- return $msg;
+ return $out;
}
public function shouldCheckMaxlag() {
@@ -131,39 +625,40 @@ class ApiHelp extends ApiBase {
return false;
}
+ public function getCustomPrinter() {
+ $params = $this->extractRequestParams();
+ if ( $params['wrap'] ) {
+ return null;
+ }
+
+ $main = $this->getMain();
+ $errorPrinter = $main->createPrinterByName( $main->getParameter( 'format' ) );
+ return new ApiFormatRaw( $main, $errorPrinter );
+ }
+
public function getAllowedParams() {
return array(
'modules' => array(
- ApiBase::PARAM_ISMULTI => true
- ),
- 'querymodules' => array(
+ ApiBase::PARAM_DFLT => 'main',
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_DEPRECATED => true
),
+ 'submodules' => false,
+ 'recursivesubmodules' => false,
+ 'wrap' => false,
+ 'toc' => false,
);
}
- 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)',
- );
- }
-
- public function getDescription() {
- return 'Display this help screen. Or the help screen for the specified module.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
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',
+ 'action=help'
+ => 'apihelp-help-example-main',
+ 'action=help&recursivesubmodules=1'
+ => 'apihelp-help-example-recursive',
+ 'action=help&modules=help'
+ => 'apihelp-help-example-help',
+ 'action=help&modules=query+info|query+categorymembers'
+ => 'apihelp-help-example-query',
);
}
diff --git a/includes/api/ApiHelpParamValueMessage.php b/includes/api/ApiHelpParamValueMessage.php
new file mode 100644
index 00000000..7cf3d6ed
--- /dev/null
+++ b/includes/api/ApiHelpParamValueMessage.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ *
+ *
+ * Created on Dec 22, 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
+ */
+
+/**
+ * Message subclass that prepends wikitext for API help.
+ *
+ * This exists so the apihelp-*-paramvalue-*-* messages don't all have to
+ * include markup wikitext while still keeping the
+ * 'APIGetParamDescriptionMessages' hook simple.
+ *
+ * @since 1.25
+ */
+class ApiHelpParamValueMessage extends Message {
+
+ protected $paramValue;
+
+ /**
+ * @see Message::__construct
+ *
+ * @param string $paramValue Parameter value being documented
+ * @param string $text Message to use.
+ * @param array $params Parameters for the message.
+ * @throws InvalidArgumentException
+ */
+ public function __construct( $paramValue, $text, $params = array() ) {
+ parent::__construct( $text, $params );
+ $this->paramValue = $paramValue;
+ }
+
+ /**
+ * Fetch the parameter value
+ * @return string
+ */
+ public function getParamValue() {
+ return $this->paramValue;
+ }
+
+ /**
+ * Fetch the message.
+ * @return string
+ */
+ public function fetchMessage() {
+ if ( $this->message === null ) {
+ $this->message = ";{$this->paramValue}:" . parent::fetchMessage();
+ }
+ return $this->message;
+ }
+
+}
diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php
index 20396dd7..865d39fa 100644
--- a/includes/api/ApiImageRotate.php
+++ b/includes/api/ApiImageRotate.php
@@ -42,7 +42,7 @@ class ApiImageRotate extends ApiBase {
$v = $val;
}
if ( $flag !== null ) {
- $v[$flag] = '';
+ $v[$flag] = true;
}
$result[] = $v;
}
@@ -52,7 +52,8 @@ class ApiImageRotate extends ApiBase {
$params = $this->extractRequestParams();
$rotation = $params['rotation'];
- $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+ $continuationManager = new ApiContinuationManager( $this, array(), array() );
+ $this->setContinuationManager( $continuationManager );
$pageSet = $this->getPageSet();
$pageSet->execute();
@@ -70,10 +71,10 @@ class ApiImageRotate extends ApiBase {
$r['id'] = $title->getArticleID();
ApiQueryBase::addTitleInfo( $r, $title );
if ( !$title->exists() ) {
- $r['missing'] = '';
+ $r['missing'] = true;
}
- $file = wfFindFile( $title );
+ $file = wfFindFile( $title, array( 'latest' => true ) );
if ( !$file ) {
$r['result'] = 'Failure';
$r['errormessage'] = 'File does not exist';
@@ -122,7 +123,7 @@ class ApiImageRotate extends ApiBase {
$r['result'] = 'Success';
} else {
$r['result'] = 'Failure';
- $r['errormessage'] = $this->getResult()->convertStatusToArray( $status );
+ $r['errormessage'] = $this->getErrorFormatter()->arrayFromStatus( $status );
}
} else {
$r['result'] = 'Failure';
@@ -131,9 +132,11 @@ class ApiImageRotate extends ApiBase {
$result[] = $r;
}
$apiResult = $this->getResult();
- $apiResult->setIndexedTagName( $result, 'page' );
+ ApiResult::setIndexedTagName( $result, 'page' );
$apiResult->addValue( null, $this->getModuleName(), $result );
- $apiResult->endContinuation();
+
+ $this->setContinuationManager( null );
+ $continuationManager->setContinuationIntoResult( $apiResult );
}
/**
@@ -184,7 +187,9 @@ class ApiImageRotate extends ApiBase {
ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
ApiBase::PARAM_REQUIRED => true
),
- 'continue' => '',
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
@@ -193,26 +198,17 @@ class ApiImageRotate extends ApiBase {
return $result;
}
- public function getParamDescription() {
- $pageSet = $this->getPageSet();
-
- return $pageSet->getFinalParamDescription() + array(
- 'rotation' => 'Degrees to rotate image clockwise',
- 'continue' => 'When more results are available, use this to continue',
- );
- }
-
- public function getDescription() {
- return 'Rotate one or more images.';
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=imagerotate&titles=Example.jpg&rotation=90&token=123ABC',
+ 'action=imagerotate&titles=File:Example.jpg&rotation=90&token=123ABC'
+ => 'apihelp-imagerotate-example-simple',
+ 'action=imagerotate&generator=categorymembers&gcmtitle=Category:Flip&gcmtype=file&' .
+ 'rotation=180&token=123ABC'
+ => 'apihelp-imagerotate-example-generator',
);
}
}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index b11348e5..2e87d22d 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -60,7 +60,7 @@ class ApiImport extends ApiBase {
$this->dieStatus( $source );
}
- $importer = new WikiImporter( $source->value );
+ $importer = new WikiImporter( $source->value, $this->getConfig() );
if ( isset( $params['namespace'] ) ) {
$importer->setTargetNamespace( $params['namespace'] );
}
@@ -79,13 +79,13 @@ class ApiImport extends ApiBase {
try {
$importer->doImport();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$this->dieUsageMsg( array( 'import-unknownerror', $e->getMessage() ) );
}
$resultData = $reporter->getData();
$result = $this->getResult();
- $result->setIndexedTagName( $resultData, 'page' );
+ ApiResult::setIndexedTagName( $resultData, 'page' );
$result->addValue( null, $this->getModuleName(), $resultData );
}
@@ -116,36 +116,15 @@ class ApiImport extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'summary' => 'Import summary',
- 'xml' => 'Uploaded XML file',
- 'interwikisource' => 'For interwiki imports: wiki to import from',
- 'interwikipage' => 'For interwiki imports: page to import',
- 'fullhistory' => 'For interwiki imports: import the full history, not just the current version',
- 'templates' => 'For interwiki imports: import all included templates as well',
- 'namespace' => 'For interwiki imports: import to this namespace',
- 'rootpage' => 'Import as subpage of this page',
- );
- }
-
- public function getDescription() {
- return array(
- 'Import a page from another wiki, or an XML file.',
- 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
- 'sending a file for the "xml" parameter.'
- );
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&' .
+ 'action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&' .
'namespace=100&fullhistory=&token=123ABC'
- => 'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history',
+ => 'apihelp-import-example-import',
);
}
@@ -176,7 +155,7 @@ class ApiImportReporter extends ImportReporter {
if ( $title === null ) {
# Invalid or non-importable title
$r['title'] = $pageInfo['title'];
- $r['invalid'] = '';
+ $r['invalid'] = true;
} else {
ApiQueryBase::addTitleInfo( $r, $title );
$r['revisions'] = intval( $successCount );
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 976f4c12..5480d940 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -46,11 +46,12 @@ class ApiLogin extends ApiBase {
* is reached. The expiry is $this->mLoginThrottle.
*/
public function execute() {
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
$this->getResult()->addValue( null, 'login', array(
'result' => 'Aborted',
- 'reason' => 'Cannot log in when using a callback',
+ 'reason' => 'Cannot log in when the same-origin policy is not applied',
) );
return;
@@ -92,7 +93,7 @@ class ApiLogin extends ApiBase {
// @todo FIXME: Split back and frontend from this hook.
// @todo FIXME: This hook should be placed in the backend
$injected_html = '';
- wfRunHooks( 'UserLoginComplete', array( &$user, &$injected_html ) );
+ Hooks::run( 'UserLoginComplete', array( &$user, &$injected_html ) );
$result['result'] = 'Success';
$result['lguserid'] = intval( $user->getId() );
@@ -184,28 +185,12 @@ class ApiLogin extends ApiBase {
);
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
return array(
- 'name' => 'User Name',
- 'password' => 'Password',
- 'domain' => 'Domain (optional)',
- 'token' => 'Login token obtained in first request',
- );
- }
-
- 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.'
- );
- }
-
- public function getExamples() {
- return array(
- 'api.php?action=login&lgname=user&lgpassword=password'
+ 'action=login&lgname=user&lgpassword=password'
+ => 'apihelp-login-example-gettoken',
+ 'action=login&lgname=user&lgpassword=password&lgtoken=123ABC'
+ => 'apihelp-login-example-login',
);
}
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index 324f4b2f..bf0ca9c6 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -39,28 +39,17 @@ class ApiLogout extends ApiBase {
// Give extensions to do something after user logout
$injected_html = '';
- wfRunHooks( 'UserLogoutComplete', array( &$user, &$injected_html, $oldName ) );
+ Hooks::run( 'UserLogoutComplete', array( &$user, &$injected_html, $oldName ) );
}
public function isReadMode() {
return false;
}
- public function getAllowedParams() {
- return array();
- }
-
- public function getParamDescription() {
- return array();
- }
-
- public function getDescription() {
- return 'Log out and clear session data.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=logout' => 'Log the current user out',
+ 'action=logout'
+ => 'apihelp-logout-example-logout',
);
}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index 119d7b48..3bf066cf 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -42,7 +42,7 @@ class ApiMain extends ApiBase {
/**
* When no format parameter is given, this format will be used
*/
- const API_DEFAULT_FORMAT = 'xmlfm';
+ const API_DEFAULT_FORMAT = 'jsonfm';
/**
* List of available modules: action name => module class
@@ -54,6 +54,7 @@ class ApiMain extends ApiBase {
'query' => 'ApiQuery',
'expandtemplates' => 'ApiExpandTemplates',
'parse' => 'ApiParse',
+ 'stashedit' => 'ApiStashEdit',
'opensearch' => 'ApiOpenSearch',
'feedcontributions' => 'ApiFeedContributions',
'feedrecentchanges' => 'ApiFeedRecentChanges',
@@ -63,6 +64,7 @@ class ApiMain extends ApiBase {
'rsd' => 'ApiRsd',
'compare' => 'ApiComparePages',
'tokens' => 'ApiTokens',
+ 'checktoken' => 'ApiCheckToken',
// Write modules
'purge' => 'ApiPurge',
@@ -86,6 +88,8 @@ class ApiMain extends ApiBase {
'options' => 'ApiOptions',
'imagerotate' => 'ApiImageRotate',
'revisiondelete' => 'ApiRevisionDelete',
+ 'managetags' => 'ApiManageTags',
+ 'tag' => 'ApiTag',
);
/**
@@ -121,11 +125,11 @@ class ApiMain extends ApiBase {
*/
private static $mRights = array(
'writeapi' => array(
- 'msg' => 'Use of the write API',
+ 'msg' => 'right-writeapi',
'params' => array()
),
'apihighlimits' => array(
- 'msg' => 'Use higher limits in API queries (Slow queries: $1 results; Fast queries: $2 results). The limits for slow queries also apply to multivalue parameters.',
+ 'msg' => 'api-help-right-apihighlimits',
'params' => array( ApiBase::LIMIT_SML2, ApiBase::LIMIT_BIG2 )
)
);
@@ -136,7 +140,7 @@ class ApiMain extends ApiBase {
*/
private $mPrinter;
- private $mModuleMgr, $mResult;
+ private $mModuleMgr, $mResult, $mErrorFormatter, $mContinuationManager;
private $mAction;
private $mEnableWrite;
private $mInternalMode, $mSquidMaxage, $mModule;
@@ -178,15 +182,33 @@ class ApiMain extends ApiBase {
// Remove all modules other than login
global $wgUser;
- if ( $this->getVal( 'callback' ) !== null ) {
- // JSON callback allows cross-site reads.
- // For safety, strip user credentials.
- wfDebug( "API: stripping user credentials for JSON callback\n" );
+ if ( $this->lacksSameOriginSecurity() ) {
+ // If we're in a mode that breaks the same-origin policy, strip
+ // user credentials for security.
+ wfDebug( "API: stripping user credentials when the same-origin policy is not applied\n" );
$wgUser = new User();
$this->getContext()->setUser( $wgUser );
}
}
+ $uselang = $this->getParameter( 'uselang' );
+ if ( $uselang === 'user' ) {
+ // Assume the parent context is going to return the user language
+ // for uselang=user (see T85635).
+ } else {
+ if ( $uselang === 'content' ) {
+ global $wgContLang;
+ $uselang = $wgContLang->getCode();
+ }
+ $code = RequestContext::sanitizeLangCode( $uselang );
+ $this->getContext()->setLanguage( $code );
+ if ( !$this->mInternalMode ) {
+ global $wgLang;
+ $wgLang = $this->getContext()->getLanguage();
+ RequestContext::getMain()->setLanguage( $wgLang );
+ }
+ }
+
$config = $this->getConfig();
$this->mModuleMgr = new ApiModuleManager( $this );
$this->mModuleMgr->addModules( self::$Modules, 'action' );
@@ -194,7 +216,13 @@ class ApiMain extends ApiBase {
$this->mModuleMgr->addModules( self::$Formats, 'format' );
$this->mModuleMgr->addModules( $config->get( 'APIFormatModules' ), 'format' );
- $this->mResult = new ApiResult( $this );
+ Hooks::run( 'ApiMain::moduleManager', array( $this->mModuleMgr ) );
+
+ $this->mResult = new ApiResult( $this->getConfig()->get( 'APIMaxResultSize' ) );
+ $this->mErrorFormatter = new ApiErrorFormatter_BackCompat( $this->mResult );
+ $this->mResult->setErrorFormatter( $this->mErrorFormatter );
+ $this->mResult->setMainForContinuation( $this );
+ $this->mContinuationManager = null;
$this->mEnableWrite = $enableWrite;
$this->mSquidMaxage = -1; // flag for executeActionWithErrorHandling()
@@ -219,6 +247,43 @@ class ApiMain extends ApiBase {
}
/**
+ * Get the ApiErrorFormatter object associated with current request
+ * @return ApiErrorFormatter
+ */
+ public function getErrorFormatter() {
+ return $this->mErrorFormatter;
+ }
+
+ /**
+ * Get the continuation manager
+ * @return ApiContinuationManager|null
+ */
+ public function getContinuationManager() {
+ return $this->mContinuationManager;
+ }
+
+ /**
+ * Set the continuation manager
+ * @param ApiContinuationManager|null
+ */
+ public function setContinuationManager( $manager ) {
+ if ( $manager !== null ) {
+ if ( !$manager instanceof ApiContinuationManager ) {
+ throw new InvalidArgumentException( __METHOD__ . ': Was passed ' .
+ is_object( $manager ) ? get_class( $manager ) : gettype( $manager )
+ );
+ }
+ if ( $this->mContinuationManager !== null ) {
+ throw new UnexpectedValueException(
+ __METHOD__ . ': tried to set manager from ' . $manager->getSource() .
+ ' when a manager is already set from ' . $this->mContinuationManager->getSource()
+ );
+ }
+ }
+ $this->mContinuationManager = $manager;
+ }
+
+ /**
* Get the API module object. Only works after executeAction()
*
* @return ApiBase
@@ -290,6 +355,16 @@ class ApiMain extends ApiBase {
}
}
+ if ( $mode === 'public' && $this->getParameter( 'uselang' ) === 'user' ) {
+ // User language is used for i18n, so we don't want to publicly
+ // cache. Anons are ok, because if they have non-default language
+ // then there's an appropriate Vary header set by whatever set
+ // their non-default language.
+ wfDebug( __METHOD__ . ": downgrading cache mode 'public' to " .
+ "'anon-public-user-private' due to uselang=user\n" );
+ $mode = 'anon-public-user-private';
+ }
+
wfDebug( __METHOD__ . ": setting cache mode $mode\n" );
$this->mCacheMode = $mode;
}
@@ -328,14 +403,11 @@ class ApiMain extends ApiBase {
* Execute api request. Any errors will be handled if the API was called by the remote client.
*/
public function execute() {
- $this->profileIn();
if ( $this->mInternalMode ) {
$this->executeAction();
} else {
$this->executeActionWithErrorHandling();
}
-
- $this->profileOut();
}
/**
@@ -373,10 +445,6 @@ class ApiMain extends ApiBase {
// avoid sending public cache headers for errors.
$this->sendCacheHeaders();
- if ( $this->mPrinter->getIsHtml() && !$this->mPrinter->isDisabled() ) {
- echo wfReportTime();
- }
-
ob_end_flush();
}
@@ -390,11 +458,17 @@ class ApiMain extends ApiBase {
// 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 );
+ try {
+ MWExceptionHandler::rollbackMasterChangesAndLog( $e );
+ } catch ( DBError $e2 ) {
+ // Rollback threw an exception too. Log it, but don't interrupt
+ // our regularly scheduled exception handling.
+ MWExceptionHandler::logException( $e2 );
+ }
}
// Allow extra cleanup and logging
- wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
+ Hooks::run( 'ApiMain::onException', array( $this, $e ) );
// Log it
if ( !( $e instanceof UsageException ) ) {
@@ -421,9 +495,22 @@ class ApiMain extends ApiBase {
// 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 );
+ // Printer may not be initialized if the extractRequestParams() fails for the main module
+ $this->createErrorPrinter();
+
+ try {
+ $this->printResult( true );
+ } catch ( UsageException $ex ) {
+ // The error printer itself is failing. Try suppressing its request
+ // parameters and redo.
+ $this->setWarning(
+ 'Error printer failed (will retry without params): ' . $ex->getMessage()
+ );
+ $this->mPrinter = null;
+ $this->createErrorPrinter();
+ $this->mPrinter->forceDefaultParams();
+ $this->printResult( true );
+ }
}
/**
@@ -434,6 +521,7 @@ class ApiMain extends ApiBase {
*
* @since 1.23
* @param Exception $e
+ * @throws Exception
*/
public static function handleApiBeforeMainException( Exception $e ) {
ob_start();
@@ -462,6 +550,8 @@ class ApiMain extends ApiBase {
* If the parameter and the header do match, the header is checked against $wgCrossSiteAJAXdomains
* and $wgCrossSiteAJAXdomainExceptions, and if the origin qualifies, the appropriate CORS
* headers are set.
+ * http://www.w3.org/TR/cors/#resource-requests
+ * http://www.w3.org/TR/cors/#resource-preflight-requests
*
* @return bool False if the caller should abort (403 case), true otherwise (all other cases)
*/
@@ -474,12 +564,14 @@ class ApiMain extends ApiBase {
$request = $this->getRequest();
$response = $request->response();
+
// Origin: header is a space-separated list of origins, check all of them
$originHeader = $request->getHeader( 'Origin' );
if ( $originHeader === false ) {
$origins = array();
} else {
- $origins = explode( ' ', $originHeader );
+ $originHeader = trim( $originHeader );
+ $origins = preg_split( '/\s+/', $originHeader );
}
if ( !in_array( $originParam, $origins ) ) {
@@ -494,18 +586,44 @@ class ApiMain extends ApiBase {
}
$config = $this->getConfig();
- $matchOrigin = self::matchOrigin(
+ $matchOrigin = count( $origins ) === 1 && self::matchOrigin(
$originParam,
$config->get( 'CrossSiteAJAXdomains' ),
$config->get( 'CrossSiteAJAXdomainExceptions' )
);
if ( $matchOrigin ) {
- $response->header( "Access-Control-Allow-Origin: $originParam" );
+ $requestedMethod = $request->getHeader( 'Access-Control-Request-Method' );
+ $preflight = $request->getMethod() === 'OPTIONS' && $requestedMethod !== false;
+ if ( $preflight ) {
+ // This is a CORS preflight request
+ if ( $requestedMethod !== 'POST' && $requestedMethod !== 'GET' ) {
+ // If method is not a case-sensitive match, do not set any additional headers and terminate.
+ return true;
+ }
+ // We allow the actual request to send the following headers
+ $requestedHeaders = $request->getHeader( 'Access-Control-Request-Headers' );
+ if ( $requestedHeaders !== false ) {
+ if ( !self::matchRequestedHeaders( $requestedHeaders ) ) {
+ return true;
+ }
+ $response->header( 'Access-Control-Allow-Headers: ' . $requestedHeaders );
+ }
+
+ // We only allow the actual request to be GET or POST
+ $response->header( 'Access-Control-Allow-Methods: POST, GET' );
+ }
+
+ $response->header( "Access-Control-Allow-Origin: $originHeader" );
$response->header( 'Access-Control-Allow-Credentials: true' );
- $this->getOutput()->addVaryHeader( 'Origin' );
+ $response->header( "Timing-Allow-Origin: $originHeader" ); # http://www.w3.org/TR/resource-timing/#timing-allow-origin
+
+ if ( !$preflight ) {
+ $response->header( 'Access-Control-Expose-Headers: MediaWiki-API-Error, Retry-After, X-Database-Lag' );
+ }
}
+ $this->getOutput()->addVaryHeader( 'Origin' );
return true;
}
@@ -535,6 +653,41 @@ class ApiMain extends ApiBase {
}
/**
+ * Attempt to validate the value of Access-Control-Request-Headers against a list
+ * of headers that we allow the follow up request to send.
+ *
+ * @param string $requestedHeaders Comma seperated list of HTTP headers
+ * @return bool True if all requested headers are in the list of allowed headers
+ */
+ protected static function matchRequestedHeaders( $requestedHeaders ) {
+ if ( trim( $requestedHeaders ) === '' ) {
+ return true;
+ }
+ $requestedHeaders = explode( ',', $requestedHeaders );
+ $allowedAuthorHeaders = array_flip( array(
+ /* simple headers (see spec) */
+ 'accept',
+ 'accept-language',
+ 'content-language',
+ 'content-type',
+ /* non-authorable headers in XHR, which are however requested by some UAs */
+ 'accept-encoding',
+ 'dnt',
+ 'origin',
+ /* MediaWiki whitelist */
+ 'api-user-agent',
+ ) );
+ foreach ( $requestedHeaders as $rHeader ) {
+ $rHeader = strtolower( trim( $rHeader ) );
+ if ( !isset( $allowedAuthorHeaders[$rHeader] ) ) {
+ wfDebugLog( 'api', 'CORS preflight failed on requested header: ' . $rHeader );
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Helper function to convert wildcard string into a regex
* '*' => '.*?'
* '?' => '.'
@@ -563,8 +716,24 @@ class ApiMain extends ApiBase {
$out->addVaryHeader( 'X-Forwarded-Proto' );
}
+ // The logic should be:
+ // $this->mCacheControl['max-age'] is set?
+ // Use it, the module knows better than our guess.
+ // !$this->mModule || $this->mModule->isWriteMode(), and mCacheMode is private?
+ // Use 0 because we can guess caching is probably the wrong thing to do.
+ // Use $this->getParameter( 'maxage' ), which already defaults to 0.
+ $maxage = 0;
+ if ( isset( $this->mCacheControl['max-age'] ) ) {
+ $maxage = $this->mCacheControl['max-age'];
+ } elseif ( ( $this->mModule && !$this->mModule->isWriteMode() ) ||
+ $this->mCacheMode !== 'private'
+ ) {
+ $maxage = $this->getParameter( 'maxage' );
+ }
+ $privateCache = 'private, must-revalidate, max-age=' . $maxage;
+
if ( $this->mCacheMode == 'private' ) {
- $response->header( 'Cache-Control: private' );
+ $response->header( "Cache-Control: $privateCache" );
return;
}
@@ -576,14 +745,14 @@ class ApiMain extends ApiBase {
$response->header( $out->getXVO() );
if ( $out->haveCacheVaryCookies() ) {
// Logged in, mark this request private
- $response->header( 'Cache-Control: private' );
+ $response->header( "Cache-Control: $privateCache" );
return;
}
// Logged out, send normal public headers below
} elseif ( session_id() != '' ) {
// Logged in or otherwise has session (e.g. anonymous users who have edited)
// Mark request private
- $response->header( 'Cache-Control: private' );
+ $response->header( "Cache-Control: $privateCache" );
return;
} // else no XVO and anonymous, send public headers below
@@ -607,7 +776,7 @@ class ApiMain extends ApiBase {
// Public cache not requested
// Sending a Vary header in this case is harmless, and protects us
// against conditional calls of setCacheMaxAge().
- $response->header( 'Cache-Control: private' );
+ $response->header( "Cache-Control: $privateCache" );
return;
}
@@ -638,45 +807,39 @@ class ApiMain extends ApiBase {
}
/**
- * Replace the result data with the information about an exception.
- * Returns the error code
- * @param Exception $e
- * @return string
+ * Create the printer for error output
*/
- protected function substituteResultWithError( $e ) {
- $result = $this->getResult();
-
- // Printer may not be initialized if the extractRequestParams() fails for the main module
+ private function createErrorPrinter() {
if ( !isset( $this->mPrinter ) ) {
- // The printer has not been created yet. Try to manually get formatter value.
$value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT );
if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) {
$value = self::API_DEFAULT_FORMAT;
}
-
$this->mPrinter = $this->createPrinterByName( $value );
}
// 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() );
-
+ /**
+ * Replace the result data with the information about an exception.
+ * Returns the error code
+ * @param Exception $e
+ * @return string
+ */
+ protected function substituteResultWithError( $e ) {
+ $result = $this->getResult();
$config = $this->getConfig();
if ( $e instanceof UsageException ) {
- // User entered incorrect parameters - print usage screen
+ // User entered incorrect parameters - generate error response
$errMessage = $e->getMessageArray();
-
- // Only print the help message when this is for the developer, not runtime
- if ( $this->mPrinter->getWantsHelp() || $this->mAction == 'help' ) {
- ApiResult::setContent( $errMessage, $this->makeHelpMsg() );
- }
+ $link = wfExpandUrl( wfScript( 'api' ) );
+ ApiResult::setContentValue( $errMessage, 'docref', "See $link for API usage" );
} else {
// Something is seriously wrong
if ( ( $e instanceof DBQueryError ) && !$config->get( 'ShowSQLErrors' ) ) {
@@ -687,17 +850,19 @@ class ApiMain extends ApiBase {
$errMessage = array(
'code' => 'internal_api_error_' . get_class( $e ),
- 'info' => $info,
- );
- ApiResult::setContent(
- $errMessage,
- $config->get( 'ShowExceptionDetails' ) ? "\n\n{$e->getTraceAsString()}\n\n" : ''
+ 'info' => '[' . MWExceptionHandler::getLogId( $e ) . '] ' . $info,
);
+ if ( $config->get( 'ShowExceptionDetails' ) ) {
+ ApiResult::setContentValue(
+ $errMessage,
+ 'trace',
+ MWExceptionHandler::getRedactedTraceAsString( $e )
+ );
+ }
}
// Remember all the warnings to re-add them later
- $oldResult = $result->getData();
- $warnings = isset( $oldResult['warnings'] ) ? $oldResult['warnings'] : null;
+ $warnings = $result->getResultData( array( 'warnings' ) );
$result->reset();
// Re-add the id
@@ -756,6 +921,8 @@ class ApiMain extends ApiBase {
/**
* Set up the module for response
* @return ApiBase The module that will handle this action
+ * @throws MWException
+ * @throws UsageException
*/
protected function setupModule() {
// Instantiate the module requested by the user
@@ -856,7 +1023,7 @@ class ApiMain extends ApiBase {
// Allow extensions to stop execution for arbitrary reasons.
$message = false;
- if ( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) {
+ if ( !Hooks::run( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) {
$this->dieUsageMsg( $message );
}
}
@@ -928,10 +1095,8 @@ class ApiMain extends ApiBase {
$this->checkAsserts( $params );
// Execute
- $module->profileIn();
$module->execute();
- wfRunHooks( 'APIAfterExecute', array( &$module ) );
- $module->profileOut();
+ Hooks::run( 'APIAfterExecute', array( &$module ) );
$this->reportUnusedParams();
@@ -1080,23 +1245,10 @@ class ApiMain extends ApiBase {
$this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' );
}
- $this->getResult()->cleanUpUTF8();
$printer = $this->mPrinter;
- $printer->profileIn();
-
- /**
- * If the help message is requested in the default (xmlfm) format,
- * tell the printer not to escape ampersands so that our links do
- * not break.
- */
- $isHelp = $isError || $this->mAction == 'help';
- $printer->setUnescapeAmps( $isHelp && $printer->getFormat() == 'XML' && $printer->getIsHtml() );
-
- $printer->initPrinter( $isHelp );
-
+ $printer->initPrinter( false );
$printer->execute();
$printer->closePrinter();
- $printer->profileOut();
}
/**
@@ -1113,14 +1265,14 @@ class ApiMain extends ApiBase {
*/
public function getAllowedParams() {
return array(
- 'format' => array(
- ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
- ApiBase::PARAM_TYPE => 'submodule',
- ),
'action' => array(
ApiBase::PARAM_DFLT => 'help',
ApiBase::PARAM_TYPE => 'submodule',
),
+ 'format' => array(
+ ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT,
+ ApiBase::PARAM_TYPE => 'submodule',
+ ),
'maxlag' => array(
ApiBase::PARAM_TYPE => 'integer'
),
@@ -1139,126 +1291,136 @@ class ApiMain extends ApiBase {
'servedby' => false,
'curtimestamp' => false,
'origin' => null,
+ 'uselang' => array(
+ ApiBase::PARAM_DFLT => 'user',
+ ),
);
}
- /**
- * See ApiBase for description.
- *
- * @return array
- */
- public function getParamDescription() {
+ /** @see ApiBase::getExamplesMessages() */
+ protected function getExamplesMessages() {
return array(
- 'format' => 'The format of the output',
- 'action' => 'What action you would like to perform. See below for module help',
- 'maxlag' => array(
- 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.',
- 'To save actions causing any more site replication lag, this parameter can make the client',
- 'wait until the replication lag is less than the specified value.',
- 'In case of a replag error, error code "maxlag" is returned, with the message like',
- '"Waiting for $host: $lag seconds lagged\n".',
- 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information',
- ),
- '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',
- '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.',
- ),
+ 'action=help'
+ => 'apihelp-help-example-main',
+ 'action=help&recursivesubmodules=1'
+ => 'apihelp-help-example-recursive',
);
}
+ public function modifyHelp( array &$help, array $options ) {
+ // Wish PHP had an "array_insert_before". Instead, we have to manually
+ // reindex the array to get 'permissions' in the right place.
+ $oldHelp = $help;
+ $help = array();
+ foreach ( $oldHelp as $k => $v ) {
+ if ( $k === 'submodules' ) {
+ $help['permissions'] = '';
+ }
+ $help[$k] = $v;
+ }
+ $help['credits'] = '';
+
+ // Fill 'permissions'
+ $help['permissions'] .= Html::openElement( 'div',
+ array( 'class' => 'apihelp-block apihelp-permissions' ) );
+ $m = $this->msg( 'api-help-permissions' );
+ if ( !$m->isDisabled() ) {
+ $help['permissions'] .= Html::rawElement( 'div', array( 'class' => 'apihelp-block-head' ),
+ $m->numParams( count( self::$mRights ) )->parse()
+ );
+ }
+ $help['permissions'] .= Html::openElement( 'dl' );
+ foreach ( self::$mRights as $right => $rightMsg ) {
+ $help['permissions'] .= Html::element( 'dt', null, $right );
+
+ $rightMsg = $this->msg( $rightMsg['msg'], $rightMsg['params'] )->parse();
+ $help['permissions'] .= Html::rawElement( 'dd', null, $rightMsg );
+
+ $groups = array_map( function ( $group ) {
+ return $group == '*' ? 'all' : $group;
+ }, User::getGroupsWithPermission( $right ) );
+
+ $help['permissions'] .= Html::rawElement( 'dd', null,
+ $this->msg( 'api-help-permissions-granted-to' )
+ ->numParams( count( $groups ) )
+ ->params( $this->getLanguage()->commaList( $groups ) )
+ ->parse()
+ );
+ }
+ $help['permissions'] .= Html::closeElement( 'dl' );
+ $help['permissions'] .= Html::closeElement( 'div' );
+
+ // Fill 'credits', if applicable
+ if ( empty( $options['nolead'] ) ) {
+ $help['credits'] .= Html::element( 'h' . min( 6, $options['headerlevel'] + 1 ),
+ array( 'id' => '+credits', 'class' => 'apihelp-header' ),
+ $this->msg( 'api-credits-header' )->parse()
+ );
+ $help['credits'] .= $this->msg( 'api-credits' )->useDatabase( false )->parseAsBlock();
+ }
+ }
+
+ private $mCanApiHighLimits = null;
+
/**
- * See ApiBase for description.
- *
- * @return array
+ * Check whether the current user is allowed to use high limits
+ * @return bool
*/
- public function getDescription() {
- return array(
- '',
- '',
- '**********************************************************************************************',
- '** **',
- '** 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.',
- '',
- '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.',
- '',
- ' In the case of an invalid action being passed, these will have a value',
- ' of "unknown_action".',
- '',
- ' 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',
- '',
- '',
- '',
- '',
- '',
- );
+ public function canApiHighLimits() {
+ if ( !isset( $this->mCanApiHighLimits ) ) {
+ $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' );
+ }
+
+ return $this->mCanApiHighLimits;
}
/**
- * Returns an array of strings with credits for the API
- * @return array
+ * Overrides to return this instance's module manager.
+ * @return ApiModuleManager
*/
- protected function getCredits() {
- return array(
- 'API developers:',
- ' 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/'
+ public function getModuleManager() {
+ return $this->mModuleMgr;
+ }
+
+ /**
+ * Fetches the user agent used for this request
+ *
+ * The value will be the combination of the 'Api-User-Agent' header (if
+ * any) and the standard User-Agent header (if any).
+ *
+ * @return string
+ */
+ public function getUserAgent() {
+ return trim(
+ $this->getRequest()->getHeader( 'Api-user-agent' ) . ' ' .
+ $this->getRequest()->getHeader( 'User-agent' )
);
}
+ /************************************************************************//**
+ * @name Deprecated
+ * @{
+ */
+
/**
* Sets whether the pretty-printer should format *bold* and $italics$
*
+ * @deprecated since 1.25
* @param bool $help
*/
public function setHelp( $help = true ) {
+ wfDeprecated( __METHOD__, '1.25' );
$this->mPrinter->setHelp( $help );
}
/**
* Override the parent to generate help messages for all available modules.
*
+ * @deprecated since 1.25
* @return string
*/
public function makeHelpMsg() {
+ wfDeprecated( __METHOD__, '1.25' );
global $wgMemc;
$this->setHelp();
// Get help text from cache if present
@@ -1281,9 +1443,11 @@ class ApiMain extends ApiBase {
}
/**
+ * @deprecated since 1.25
* @return mixed|string
*/
public function reallyMakeHelpMsg() {
+ wfDeprecated( __METHOD__, '1.25' );
$this->setHelp();
// Use parent to make default message for the main module
@@ -1305,8 +1469,12 @@ class ApiMain extends ApiBase {
$msg .= "\n$astriks Permissions $astriks\n\n";
foreach ( self::$mRights as $right => $rightMsg ) {
+ $rightsMsg = $this->msg( $rightMsg['msg'], $rightMsg['params'] )
+ ->useDatabase( false )
+ ->inLanguage( 'en' )
+ ->text();
$groups = User::getGroupsWithPermission( $right );
- $msg .= "* " . $right . " *\n " . wfMsgReplaceArgs( $rightMsg['msg'], $rightMsg['params'] ) .
+ $msg .= "* " . $right . " *\n $rightsMsg" .
"\nGranted to:\n " . str_replace( '*', 'all', implode( ', ', $groups ) ) . "\n\n";
}
@@ -1321,18 +1489,22 @@ class ApiMain extends ApiBase {
$msg .= "\n";
}
- $msg .= "\n*** Credits: ***\n " . implode( "\n ", $this->getCredits() ) . "\n";
+ $credits = $this->msg( 'api-credits' )->useDatabase( 'false' )->inLanguage( 'en' )->text();
+ $credits = str_replace( "\n", "\n ", $credits );
+ $msg .= "\n*** Credits: ***\n $credits\n";
return $msg;
}
/**
+ * @deprecated since 1.25
* @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 ) {
+ wfDeprecated( __METHOD__, '1.25' );
$modulePrefix = $module->getModulePrefix();
if ( strval( $modulePrefix ) !== '' ) {
$modulePrefix = "($modulePrefix) ";
@@ -1341,20 +1513,6 @@ class ApiMain extends ApiBase {
return "* $paramName={$module->getModuleName()} $modulePrefix*";
}
- private $mCanApiHighLimits = null;
-
- /**
- * Check whether the current user is allowed to use high limits
- * @return bool
- */
- public function canApiHighLimits() {
- if ( !isset( $this->mCanApiHighLimits ) ) {
- $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' );
- }
-
- return $this->mCanApiHighLimits;
- }
-
/**
* Check whether the user wants us to show version information in the API help
* @return bool
@@ -1367,14 +1525,6 @@ class ApiMain extends ApiBase {
}
/**
- * Overrides to return this instance's module manager.
- * @return ApiModuleManager
- */
- public function getModuleManager() {
- return $this->mModuleMgr;
- }
-
- /**
* Add or overwrite a module in this ApiMain instance. Intended for use by extending
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
@@ -1418,11 +1568,13 @@ class ApiMain extends ApiBase {
public function getFormats() {
return $this->getModuleManager()->getNamesWithClasses( 'format' );
}
+
+ /**@}*/
+
}
/**
* This exception will be thrown when dieUsage is called to stop module execution.
- * The exception handling code will print a help screen explaining how this API may be used.
*
* @ingroup API
*/
@@ -1476,3 +1628,8 @@ class UsageException extends MWException {
return "{$this->getCodeString()}: {$this->getMessage()}";
}
}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */
diff --git a/includes/api/ApiManageTags.php b/includes/api/ApiManageTags.php
new file mode 100644
index 00000000..240d3506
--- /dev/null
+++ b/includes/api/ApiManageTags.php
@@ -0,0 +1,107 @@
+<?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 API
+ * @since 1.25
+ */
+class ApiManageTags extends ApiBase {
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ // make sure the user is allowed
+ if ( !$this->getUser()->isAllowed( 'managechangetags' ) ) {
+ $this->dieUsage( "You don't have permission to manage change tags", 'permissiondenied' );
+ }
+
+ $result = $this->getResult();
+ $funcName = "{$params['operation']}TagWithChecks";
+ $status = ChangeTags::$funcName( $params['tag'], $params['reason'],
+ $this->getUser(), $params['ignorewarnings'] );
+
+ if ( !$status->isOK() ) {
+ $this->dieStatus( $status );
+ }
+
+ $ret = array(
+ 'operation' => $params['operation'],
+ 'tag' => $params['tag'],
+ );
+ if ( !$status->isGood() ) {
+ $ret['warnings'] = $this->getErrorFormatter()->arrayFromStatus( $status, 'warning' );
+ }
+ $ret['success'] = $status->value !== null;
+ if ( $ret['success'] ) {
+ $ret['logid'] = $status->value;
+ }
+ $result->addValue( null, $this->getModuleName(), $ret );
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'operation' => array(
+ ApiBase::PARAM_TYPE => array( 'create', 'delete', 'activate', 'deactivate' ),
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'tag' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'reason' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
+ 'ignorewarnings' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false,
+ ),
+ );
+ }
+
+ public function needsToken() {
+ return 'csrf';
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=managetags&operation=create&tag=spam&reason=For+use+in+edit+patrolling&token=123ABC'
+ => 'apihelp-managetags-example-create',
+ 'action=managetags&operation=delete&tag=vandlaism&reason=Misspelt&token=123ABC'
+ => 'apihelp-managetags-example-delete',
+ 'action=managetags&operation=activate&tag=spam&reason=For+use+in+edit+patrolling&token=123ABC'
+ => 'apihelp-managetags-example-activate',
+ 'action=managetags&operation=deactivate&tag=spam&reason=No+longer+required&token=123ABC'
+ => 'apihelp-managetags-example-deactivate',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Tag_management';
+ }
+}
diff --git a/includes/api/ApiMessage.php b/includes/api/ApiMessage.php
new file mode 100644
index 00000000..6717c390
--- /dev/null
+++ b/includes/api/ApiMessage.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ * Defines an interface for messages with additional machine-readable data for
+ * use by the API, and provides concrete implementations of that interface.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * 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 messages with machine-readable data for use by the API
+ * @since 1.25
+ * @ingroup API
+ */
+interface IApiMessage extends MessageSpecifier {
+ /**
+ * Returns a machine-readable code for use by the API
+ *
+ * The message key is often sufficient, but sometimes there are multiple
+ * messages used for what is really the same underlying condition (e.g.
+ * badaccess-groups and badaccess-group0)
+ * @return string
+ */
+ public function getApiCode();
+
+ /**
+ * Returns additional machine-readable data about the error condition
+ * @return array
+ */
+ public function getApiData();
+
+ /**
+ * Sets the machine-readable code for use by the API
+ * @param string|null $code If null, the message key should be returned by self::getApiCode()
+ * @param array|null $data If non-null, passed to self::setApiData()
+ */
+ public function setApiCode( $code, array $data = null );
+
+ /**
+ * Sets additional machine-readable data about the error condition
+ * @param array $data
+ */
+ public function setApiData( array $data );
+}
+
+/**
+ * Extension of Message implementing IApiMessage
+ * @since 1.25
+ * @ingroup API
+ * @todo: Would be nice to use a Trait here to avoid code duplication
+ */
+class ApiMessage extends Message implements IApiMessage {
+ protected $apiCode = null;
+ protected $apiData = array();
+
+ /**
+ * Create an IApiMessage for the message
+ *
+ * This returns $msg if it's an IApiMessage, calls 'new ApiRawMessage' if
+ * $msg is a RawMessage, or calls 'new ApiMessage' in all other cases.
+ *
+ * @param Message|RawMessage|array|string $msg
+ * @param string|null $code
+ * @param array|null $data
+ * @return ApiMessage
+ */
+ public static function create( $msg, $code = null, array $data = null ) {
+ if ( $msg instanceof IApiMessage ) {
+ return $msg;
+ } elseif ( $msg instanceof RawMessage ) {
+ return new ApiRawMessage( $msg, $code, $data );
+ } else {
+ return new ApiMessage( $msg, $code, $data );
+ }
+ }
+
+ /**
+ * @param Message|string|array $msg
+ * - Message: is cloned
+ * - array: first element is $key, rest are $params to Message::__construct
+ * - string: passed to Message::__construct
+ * @param string|null $code
+ * @param array|null $data
+ * @return ApiMessage
+ */
+ public function __construct( $msg, $code = null, array $data = null ) {
+ if ( $msg instanceof Message ) {
+ foreach ( get_class_vars( get_class( $this ) ) as $key => $value ) {
+ if ( isset( $msg->$key ) ) {
+ $this->$key = $msg->$key;
+ }
+ }
+ } elseif ( is_array( $msg ) ) {
+ $key = array_shift( $msg );
+ parent::__construct( $key, $msg );
+ } else {
+ parent::__construct( $msg );
+ }
+ $this->apiCode = $code;
+ $this->apiData = (array)$data;
+ }
+
+ public function getApiCode() {
+ return $this->apiCode === null ? $this->getKey() : $this->apiCode;
+ }
+
+ public function setApiCode( $code, array $data = null ) {
+ $this->apiCode = $code;
+ if ( $data !== null ) {
+ $this->setApiData( $data );
+ }
+ }
+
+ public function getApiData() {
+ return $this->apiData;
+ }
+
+ public function setApiData( array $data ) {
+ $this->apiData = $data;
+ }
+}
+
+/**
+ * Extension of RawMessage implementing IApiMessage
+ * @since 1.25
+ * @ingroup API
+ * @todo: Would be nice to use a Trait here to avoid code duplication
+ */
+class ApiRawMessage extends RawMessage implements IApiMessage {
+ protected $apiCode = null;
+ protected $apiData = array();
+
+ /**
+ * @param RawMessage|string|array $msg
+ * - RawMessage: is cloned
+ * - array: first element is $key, rest are $params to RawMessage::__construct
+ * - string: passed to RawMessage::__construct
+ * @param string|null $code
+ * @param array|null $data
+ * @return ApiMessage
+ */
+ public function __construct( $msg, $code = null, array $data = null ) {
+ if ( $msg instanceof RawMessage ) {
+ foreach ( get_class_vars( get_class( $this ) ) as $key => $value ) {
+ if ( isset( $msg->$key ) ) {
+ $this->$key = $msg->$key;
+ }
+ }
+ } elseif ( is_array( $msg ) ) {
+ $key = array_shift( $msg );
+ parent::__construct( $key, $msg );
+ } else {
+ parent::__construct( $msg );
+ }
+ $this->apiCode = $code;
+ $this->apiData = (array)$data;
+ }
+
+ public function getApiCode() {
+ return $this->apiCode === null ? $this->getKey() : $this->apiCode;
+ }
+
+ public function setApiCode( $code, array $data = null ) {
+ $this->apiCode = $code;
+ if ( $data !== null ) {
+ $this->setApiData( $data );
+ }
+ }
+
+ public function getApiData() {
+ return $this->apiData;
+ }
+
+ public function setApiData( array $data ) {
+ $this->apiData = $data;
+ }
+}
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index 04e931d2..e42958bf 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -72,9 +72,9 @@ class ApiMove extends ApiBase {
// Move the page
$toTitleExists = $toTitle->exists();
- $retval = $fromTitle->moveTo( $toTitle, true, $params['reason'], !$params['noredirect'] );
- if ( $retval !== true ) {
- $this->dieUsageMsg( reset( $retval ) );
+ $status = $this->movePage( $fromTitle, $toTitle, $params['reason'], !$params['noredirect'] );
+ if ( !$status->isOK() ) {
+ $this->dieStatus( $status );
}
$r = array(
@@ -83,34 +83,28 @@ class ApiMove extends ApiBase {
'reason' => $params['reason']
);
- if ( $fromTitle->exists() ) {
- //NOTE: we assume that if the old title exists, it's because it was re-created as
- // a redirect to the new title. This is not safe, but what we did before was
- // even worse: we just determined whether a redirect should have been created,
- // and reported that it was created if it should have, without any checks.
- // Also note that isRedirect() is unreliable because of bug 37209.
- $r['redirectcreated'] = '';
- }
+ //NOTE: we assume that if the old title exists, it's because it was re-created as
+ // a redirect to the new title. This is not safe, but what we did before was
+ // even worse: we just determined whether a redirect should have been created,
+ // and reported that it was created if it should have, without any checks.
+ // Also note that isRedirect() is unreliable because of bug 37209.
+ $r['redirectcreated'] = $fromTitle->exists();
- if ( $toTitleExists ) {
- $r['moveoverredirect'] = '';
- }
+ $r['moveoverredirect'] = $toTitleExists;
// Move the talk page
if ( $params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
$toTalkExists = $toTalk->exists();
- $retval = $fromTalk->moveTo( $toTalk, true, $params['reason'], !$params['noredirect'] );
- if ( $retval === true ) {
+ $status = $this->movePage( $fromTalk, $toTalk, $params['reason'], !$params['noredirect'] );
+ if ( $status->isOK() ) {
$r['talkfrom'] = $fromTalk->getPrefixedText();
$r['talkto'] = $toTalk->getPrefixedText();
- if ( $toTalkExists ) {
- $r['talkmoveoverredirect'] = '';
- }
+ $r['talkmoveoverredirect'] = $toTalkExists;
} else {
// We're not gonna dieUsage() on failure, since we already changed something
- $parsed = $this->parseMsg( reset( $retval ) );
- $r['talkmove-error-code'] = $parsed['code'];
- $r['talkmove-error-info'] = $parsed['info'];
+ $error = $this->getErrorFromStatus( $status );
+ $r['talkmove-error-code'] = $error[0];
+ $r['talkmove-error-info'] = $error[1];
}
}
@@ -120,12 +114,12 @@ class ApiMove extends ApiBase {
if ( $params['movesubpages'] ) {
$r['subpages'] = $this->moveSubpages( $fromTitle, $toTitle,
$params['reason'], $params['noredirect'] );
- $result->setIndexedTagName( $r['subpages'], 'subpage' );
+ ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
if ( $params['movetalk'] ) {
$r['subpages-talk'] = $this->moveSubpages( $fromTalk, $toTalk,
$params['reason'], $params['noredirect'] );
- $result->setIndexedTagName( $r['subpages-talk'], 'subpage' );
+ ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
}
}
@@ -148,6 +142,33 @@ class ApiMove extends ApiBase {
}
/**
+ * @param Title $from
+ * @param Title $to
+ * @param string $reason
+ * @param bool $createRedirect
+ * @return Status
+ */
+ protected function movePage( Title $from, Title $to, $reason, $createRedirect ) {
+ $mp = new MovePage( $from, $to );
+ $valid = $mp->isValidMove();
+ if ( !$valid->isOK() ) {
+ return $valid;
+ }
+
+ $permStatus = $mp->checkPermissions( $this->getUser(), $reason );
+ if ( !$permStatus->isOK() ) {
+ return $permStatus;
+ }
+
+ // Check suppressredirect permission
+ if ( !$this->getUser()->isAllowed( 'suppressredirect' ) ) {
+ $createRedirect = true;
+ }
+
+ return $mp->move( $this->getUser(), $reason, $createRedirect );
+ }
+
+ /**
* @param Title $fromTitle
* @param Title $toTitle
* @param string $reason
@@ -220,37 +241,15 @@ 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',
- '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',
- 'ignorewarnings' => 'Ignore any warnings'
- );
- }
-
- public function getDescription() {
- return 'Move a page.';
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
+ 'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
'reason=Misspelled%20title&movetalk=&noredirect='
+ => 'apihelp-move-example-move',
);
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index 7fb045e3..a93b7cc6 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -3,6 +3,8 @@
* Created on Oct 13, 2006
*
* Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ * Copyright © 2008 Brion Vibber <brion@wikimedia.org>
+ * 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
@@ -27,21 +29,50 @@
*/
class ApiOpenSearch extends ApiBase {
+ private $format = null;
+ private $fm = null;
+
/**
- * Override built-in handling of format parameter.
- * Only JSON is supported.
+ * Get the output format
*
- * @return ApiFormatBase
+ * @return string
*/
- public function getCustomPrinter() {
- $params = $this->extractRequestParams();
- $format = $params['format'];
- $allowed = array( 'json', 'jsonfm' );
- if ( in_array( $format, $allowed ) ) {
- return $this->getMain()->createPrinterByName( $format );
+ protected function getFormat() {
+ if ( $this->format === null ) {
+ $params = $this->extractRequestParams();
+ $format = $params['format'];
+
+ $allowedParams = $this->getAllowedParams();
+ if ( !in_array( $format, $allowedParams['format'][ApiBase::PARAM_TYPE] ) ) {
+ $format = $allowedParams['format'][ApiBase::PARAM_DFLT];
+ }
+
+ if ( substr( $format, -2 ) === 'fm' ) {
+ $this->format = substr( $format, 0, -2 );
+ $this->fm = 'fm';
+ } else {
+ $this->format = $format;
+ $this->fm = '';
+ }
}
+ return $this->format;
+ }
+
+ public function getCustomPrinter() {
+ switch ( $this->getFormat() ) {
+ case 'json':
+ return new ApiOpenSearchFormatJson(
+ $this->getMain(), $this->fm, $this->getParameter( 'warningsaserror' )
+ );
+
+ case 'xml':
+ $printer = $this->getMain()->createPrinterByName( 'xml' . $this->fm );
+ $printer->setRootElement( 'SearchSuggestion' );
+ return $printer;
- return $this->getMain()->createPrinterByName( $allowed[0] );
+ default:
+ ApiBase::dieDebug( __METHOD__, "Unsupported format '{$this->getFormat()}'" );
+ }
}
public function execute() {
@@ -51,21 +82,188 @@ class ApiOpenSearch extends ApiBase {
$namespaces = $params['namespace'];
$suggest = $params['suggest'];
- // Some script that was loaded regardless of wgEnableOpenSearchSuggest, likely cached.
- if ( $suggest && !$this->getConfig()->get( 'EnableOpenSearchSuggest' ) ) {
- $searches = array();
+ if ( $params['redirects'] === null ) {
+ // Backwards compatibility, don't resolve for JSON.
+ $resolveRedir = $this->getFormat() !== 'json';
} else {
+ $resolveRedir = $params['redirects'] === 'resolve';
+ }
+
+ $results = array();
+
+ if ( !$suggest || $this->getConfig()->get( 'EnableOpenSearchSuggest' ) ) {
// Open search results may be stored for a very long time
$this->getMain()->setCacheMaxAge( $this->getConfig()->get( 'SearchSuggestCacheExpiry' ) );
$this->getMain()->setCacheMode( 'public' );
+ $this->search( $search, $limit, $namespaces, $resolveRedir, $results );
+
+ // Allow hooks to populate extracts and images
+ Hooks::run( 'ApiOpenSearchSuggest', array( &$results ) );
+
+ // Trim extracts, if necessary
+ $length = $this->getConfig()->get( 'OpenSearchDescriptionLength' );
+ foreach ( $results as &$r ) {
+ if ( is_string( $r['extract'] ) && !$r['extract trimmed'] ) {
+ $r['extract'] = self::trimExtract( $r['extract'], $length );
+ }
+ }
+ }
+
+ // Populate result object
+ $this->populateResult( $search, $results );
+ }
- $searcher = new StringPrefixSearch;
- $searches = $searcher->searchWithVariants( $search, $limit, $namespaces );
+ /**
+ * Perform the search
+ *
+ * @param string $search Text to search
+ * @param int $limit Maximum items to return
+ * @param array $namespaces Namespaces to search
+ * @param bool $resolveRedir Whether to resolve redirects
+ * @param array &$results Put results here. Keys have to be integers.
+ */
+ protected function search( $search, $limit, $namespaces, $resolveRedir, &$results ) {
+ // Find matching titles as Title objects
+ $searcher = new TitlePrefixSearch;
+ $titles = $searcher->searchWithVariants( $search, $limit, $namespaces );
+ if ( !$titles ) {
+ return;
}
- // Set top level elements
+
+ // Special pages need unique integer ids in the return list, so we just
+ // assign them negative numbers because those won't clash with the
+ // always positive articleIds that non-special pages get.
+ $nextSpecialPageId = -1;
+
+ if ( $resolveRedir ) {
+ // Query for redirects
+ $redirects = array();
+ $lb = new LinkBatch( $titles );
+ if ( !$lb->isEmpty() ) {
+ $db = $this->getDb();
+ $res = $db->select(
+ array( 'page', 'redirect' ),
+ array( 'page_namespace', 'page_title', 'rd_namespace', 'rd_title' ),
+ array(
+ 'rd_from = page_id',
+ 'rd_interwiki IS NULL OR rd_interwiki = ' . $db->addQuotes( '' ),
+ $lb->constructSet( 'page', $db ),
+ ),
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ $redirects[$row->page_namespace][$row->page_title] =
+ array( $row->rd_namespace, $row->rd_title );
+ }
+ }
+
+ // Bypass any redirects
+ $seen = array();
+ foreach ( $titles as $title ) {
+ $ns = $title->getNamespace();
+ $dbkey = $title->getDBkey();
+ $from = null;
+ if ( isset( $redirects[$ns][$dbkey] ) ) {
+ list( $ns, $dbkey ) = $redirects[$ns][$dbkey];
+ $from = $title;
+ $title = Title::makeTitle( $ns, $dbkey );
+ }
+ if ( !isset( $seen[$ns][$dbkey] ) ) {
+ $seen[$ns][$dbkey] = true;
+ $resultId = $title->getArticleId();
+ if ( $resultId === 0 ) {
+ $resultId = $nextSpecialPageId;
+ $nextSpecialPageId -= 1;
+ }
+ $results[$resultId] = array(
+ 'title' => $title,
+ 'redirect from' => $from,
+ 'extract' => false,
+ 'extract trimmed' => false,
+ 'image' => false,
+ 'url' => wfExpandUrl( $title->getFullUrl(), PROTO_CURRENT ),
+ );
+ }
+ }
+ } else {
+ foreach ( $titles as $title ) {
+ $resultId = $title->getArticleId();
+ if ( $resultId === 0 ) {
+ $resultId = $nextSpecialPageId;
+ $nextSpecialPageId -= 1;
+ }
+ $results[$resultId] = array(
+ 'title' => $title,
+ 'redirect from' => null,
+ 'extract' => false,
+ 'extract trimmed' => false,
+ 'image' => false,
+ 'url' => wfExpandUrl( $title->getFullUrl(), PROTO_CURRENT ),
+ );
+ }
+ }
+ }
+
+ /**
+ * @param string $search
+ * @param array &$results
+ */
+ protected function populateResult( $search, &$results ) {
$result = $this->getResult();
- $result->addValue( null, 0, $search );
- $result->addValue( null, 1, $searches );
+
+ switch ( $this->getFormat() ) {
+ case 'json':
+ // http://www.opensearch.org/Specifications/OpenSearch/Extensions/Suggestions/1.1
+ $result->addArrayType( null, 'array' );
+ $result->addValue( null, 0, strval( $search ) );
+ $terms = array();
+ $descriptions = array();
+ $urls = array();
+ foreach ( $results as $r ) {
+ $terms[] = $r['title']->getPrefixedText();
+ $descriptions[] = strval( $r['extract'] );
+ $urls[] = $r['url'];
+ }
+ $result->addValue( null, 1, $terms );
+ $result->addValue( null, 2, $descriptions );
+ $result->addValue( null, 3, $urls );
+ break;
+
+ case 'xml':
+ // http://msdn.microsoft.com/en-us/library/cc891508%28v=vs.85%29.aspx
+ $imageKeys = array(
+ 'source' => true,
+ 'alt' => true,
+ 'width' => true,
+ 'height' => true,
+ 'align' => true,
+ );
+ $items = array();
+ foreach ( $results as $r ) {
+ $item = array(
+ 'Text' => $r['title']->getPrefixedText(),
+ 'Url' => $r['url'],
+ );
+ if ( is_string( $r['extract'] ) && $r['extract'] !== '' ) {
+ $item['Description'] = $r['extract'];
+ }
+ if ( is_array( $r['image'] ) && isset( $r['image']['source'] ) ) {
+ $item['Image'] = array_intersect_key( $r['image'], $imageKeys );
+ }
+ ApiResult::setSubelementsList( $item, array_keys( $item ) );
+ $items[] = $item;
+ }
+ ApiResult::setIndexedTagName( $items, 'Item' );
+ $result->addValue( null, 'version', '2.0' );
+ $result->addValue( null, 'xmlns', 'http://opensearch.org/searchsuggest2' );
+ $result->addValue( null, 'Query', strval( $search ) );
+ $result->addSubelementsList( null, 'Query' );
+ $result->addValue( null, 'Section', $items );
+ break;
+
+ default:
+ ApiBase::dieDebug( __METHOD__, "Unsupported format '{$this->getFormat()}'" );
+ }
}
public function getAllowedParams() {
@@ -84,34 +282,117 @@ class ApiOpenSearch extends ApiBase {
ApiBase::PARAM_ISMULTI => true
),
'suggest' => false,
+ 'redirects' => array(
+ ApiBase::PARAM_TYPE => array( 'return', 'resolve' ),
+ ),
'format' => array(
ApiBase::PARAM_DFLT => 'json',
- ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' ),
- )
+ ApiBase::PARAM_TYPE => array( 'json', 'jsonfm', 'xml', 'xmlfm' ),
+ ),
+ 'warningsaserror' => false,
);
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
return array(
- 'search' => 'Search string',
- 'limit' => 'Maximum amount of results to return',
- 'namespace' => 'Namespaces to search',
- 'suggest' => 'Do nothing if $wgEnableOpenSearchSuggest is false',
- 'format' => 'The format of the output',
+ 'action=opensearch&search=Te'
+ => 'apihelp-opensearch-example-te',
);
}
- public function getDescription() {
- return 'Search the wiki using the OpenSearch protocol.';
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Opensearch';
}
- public function getExamples() {
- return array(
- 'api.php?action=opensearch&search=Te'
- );
+ /**
+ * Trim an extract to a sensible length.
+ *
+ * Adapted from Extension:OpenSearchXml, which adapted it from
+ * Extension:ActiveAbstract.
+ *
+ * @param string $text
+ * @param int $len Target length; actual result will continue to the end of a sentence.
+ * @return string
+ */
+ public static function trimExtract( $text, $length ) {
+ static $regex = null;
+
+ if ( $regex === null ) {
+ $endchars = array(
+ '([^\d])\.\s', '\!\s', '\?\s', // regular ASCII
+ '。', // full-width ideographic full-stop
+ '.', '!', '?', // double-width roman forms
+ '。', // half-width ideographic full stop
+ );
+ $endgroup = implode( '|', $endchars );
+ $end = "(?:$endgroup)";
+ $sentence = ".{{$length},}?$end+";
+ $regex = "/^($sentence)/u";
+ }
+
+ $matches = array();
+ if ( preg_match( $regex, $text, $matches ) ) {
+ return trim( $matches[1] );
+ } else {
+ // Just return the first line
+ $lines = explode( "\n", $text );
+ return trim( $lines[0] );
+ }
}
- public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:Opensearch';
+ /**
+ * Fetch the template for a type.
+ *
+ * @param string $type MIME type
+ * @return string
+ * @throws MWException
+ */
+ public static function getOpenSearchTemplate( $type ) {
+ global $wgOpenSearchTemplate, $wgCanonicalServer;
+
+ if ( $wgOpenSearchTemplate && $type === 'application/x-suggestions+json' ) {
+ return $wgOpenSearchTemplate;
+ }
+
+ $ns = implode( '|', SearchEngine::defaultNamespaces() );
+ if ( !$ns ) {
+ $ns = "0";
+ }
+
+ switch ( $type ) {
+ case 'application/x-suggestions+json':
+ return $wgCanonicalServer . wfScript( 'api' )
+ . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
+
+ case 'application/x-suggestions+xml':
+ return $wgCanonicalServer . wfScript( 'api' )
+ . '?action=opensearch&format=xml&search={searchTerms}&namespace=' . $ns;
+
+ default:
+ throw new MWException( __METHOD__ . ": Unknown type '$type'" );
+ }
+ }
+}
+
+class ApiOpenSearchFormatJson extends ApiFormatJson {
+ private $warningsAsError = false;
+
+ public function __construct( ApiMain $main, $fm, $warningsAsError ) {
+ parent::__construct( $main, "json$fm" );
+ $this->warningsAsError = $warningsAsError;
+ }
+
+ public function execute() {
+ if ( !$this->getResult()->getResultData( 'error' ) ) {
+ $warnings = $this->getResult()->removeValue( 'warnings', null );
+ if ( $this->warningsAsError && $warnings ) {
+ $this->dieUsage(
+ 'Warnings cannot be represented in OpenSearch JSON format', 'warnings', 0,
+ array( 'warnings' => $warnings )
+ );
+ }
+ }
+
+ parent::execute();
}
}
diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php
index b01dc3e2..8ef06299 100644
--- a/includes/api/ApiOptions.php
+++ b/includes/api/ApiOptions.php
@@ -153,30 +153,6 @@ class ApiOptions extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'reset' => 'Resets preferences to the site defaults',
- 'resetkinds' => 'List of types of options to reset when the "reset" option is set',
- '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',
- );
- }
-
- public function getDescription() {
- return array(
- '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.'
- );
- }
-
public function needsToken() {
return 'csrf';
}
@@ -185,12 +161,15 @@ class ApiOptions extends ApiBase {
return 'https://www.mediawiki.org/wiki/API:Options';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
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',
+ 'action=options&reset=&token=123ABC'
+ => 'apihelp-options-example-reset',
+ 'action=options&change=skin=vector|hideminor=1&token=123ABC'
+ => 'apihelp-options-example-change',
+ 'action=options&reset=&change=skin=monobook&optionname=nickname&' .
+ 'optionvalue=[[User:Beau|Beau]]%20([[User_talk:Beau|talk]])&token=123ABC'
+ => 'apihelp-options-example-complex',
);
}
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 0f264675..e6f218d6 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -53,7 +53,10 @@ class ApiPageSet extends ApiBase {
private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing
private $mTitles = array();
+ private $mGoodAndMissingPages = array(); // [ns][dbkey] => page_id or negative when missing
+ private $mGoodPages = array(); // [ns][dbkey] => page_id
private $mGoodTitles = array();
+ private $mMissingPages = array(); // [ns][dbkey] => fake page_id
private $mMissingTitles = array();
private $mInvalidTitles = array();
private $mMissingPageIDs = array();
@@ -65,7 +68,10 @@ class ApiPageSet extends ApiBase {
private $mPendingRedirectIDs = array();
private $mConvertedTitles = array();
private $mGoodRevIDs = array();
+ private $mLiveRevIDs = array();
+ private $mDeletedRevIDs = array();
private $mMissingRevIDs = array();
+ private $mGeneratorData = array(); // [ns][dbkey] => data array
private $mFakePageId = -1;
private $mCacheMode = 'public';
private $mRequestedPageFields = array();
@@ -90,7 +96,7 @@ class ApiPageSet extends ApiBase {
$v = $val;
}
if ( $flag !== null ) {
- $v[$flag] = '';
+ $v[$flag] = true;
}
$result[] = $v;
}
@@ -109,11 +115,9 @@ class ApiPageSet extends ApiBase {
$this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0;
$this->mDefaultNamespace = $defaultNamespace;
- $this->profileIn();
$this->mParams = $this->extractRequestParams();
$this->mResolveRedirects = $this->mParams['redirects'];
$this->mConvertTitles = $this->mParams['converttitles'];
- $this->profileOut();
}
/**
@@ -137,17 +141,12 @@ class ApiPageSet extends ApiBase {
* relevant parameters as used
*/
private function executeInternal( $isDryRun ) {
- $this->profileIn();
-
$generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null;
if ( isset( $generatorName ) ) {
$dbSource = $this->mDbSource;
- $isQuery = $dbSource instanceof ApiQuery;
- if ( !$isQuery ) {
+ if ( !$dbSource instanceof ApiQuery ) {
// If the parent container of this pageset is not ApiQuery, we must create it to run generator
$dbSource = $this->getMain()->getModuleManager()->getModule( 'query' );
- // Enable profiling for query module because it will be used for db sql profiling
- $dbSource->profileIn();
}
$generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true );
if ( $generator === null ) {
@@ -168,12 +167,9 @@ class ApiPageSet extends ApiBase {
$tmpPageSet->executeInternal( $isDryRun );
// populate this pageset with the generator output
- $this->profileOut();
- $generator->profileIn();
-
if ( !$isDryRun ) {
$generator->executeGenerator( $this );
- wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) );
+ Hooks::run( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) );
} else {
// Prevent warnings from being reported on these parameters
$main = $this->getMain();
@@ -181,17 +177,10 @@ class ApiPageSet extends ApiBase {
$main->getVal( $generator->encodeParamName( $paramName ) );
}
}
- $generator->profileOut();
- $this->profileIn();
if ( !$isDryRun ) {
$this->resolvePendingRedirects();
}
-
- if ( !$isQuery ) {
- // If this pageset is not part of the query, we called profileIn() above
- $dbSource->profileOut();
- }
} else {
// Only one of the titles/pageids/revids is allowed at the same time
$dataSource = null;
@@ -235,7 +224,6 @@ class ApiPageSet extends ApiBase {
}
}
}
- $this->profileOut();
}
/**
@@ -309,6 +297,10 @@ class ApiPageSet extends ApiBase {
$pageFlds['page_is_redirect'] = null;
}
+ if ( $this->getConfig()->get( 'ContentHandlerUseDB' ) ) {
+ $pageFlds['page_content_model'] = null;
+ }
+
// only store non-default fields
$this->mRequestedPageFields = array_diff_key( $this->mRequestedPageFields, $pageFlds );
@@ -344,6 +336,14 @@ class ApiPageSet extends ApiBase {
}
/**
+ * Returns an array [ns][dbkey] => page_id for all good titles.
+ * @return array
+ */
+ public function getGoodTitlesByNamespace() {
+ return $this->mGoodPages;
+ }
+
+ /**
* Title objects that were found in the database.
* @return Title[] Array page_id (int) => Title (obj)
*/
@@ -360,6 +360,15 @@ class ApiPageSet extends ApiBase {
}
/**
+ * Returns an array [ns][dbkey] => fake_page_id for all missing titles.
+ * fake_page_id is a unique negative number.
+ * @return array
+ */
+ public function getMissingTitlesByNamespace() {
+ return $this->mMissingPages;
+ }
+
+ /**
* Title objects that were NOT found in the database.
* The array's index will be negative for each item
* @return Title[]
@@ -369,6 +378,22 @@ class ApiPageSet extends ApiBase {
}
/**
+ * Returns an array [ns][dbkey] => page_id for all good and missing titles.
+ * @return array
+ */
+ public function getGoodAndMissingTitlesByNamespace() {
+ return $this->mGoodAndMissingPages;
+ }
+
+ /**
+ * Title objects for good and missing titles.
+ * @return array
+ */
+ public function getGoodAndMissingTitles() {
+ return $this->mGoodTitles + $this->mMissingTitles;
+ }
+
+ /**
* Titles that were deemed invalid by Title::newFromText()
* The array's index will be unique and negative for each item
* @return string[] Array of strings (not Title objects)
@@ -411,10 +436,13 @@ class ApiPageSet extends ApiBase {
if ( $titleTo->hasFragment() ) {
$r['tofragment'] = $titleTo->getFragment();
}
+ if ( $titleTo->isExternal() ) {
+ $r['tointerwiki'] = $titleTo->getInterwiki();
+ }
$values[] = $r;
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'r' );
+ ApiResult::setIndexedTagName( $values, 'r' );
}
return $values;
@@ -445,7 +473,7 @@ class ApiPageSet extends ApiBase {
);
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'n' );
+ ApiResult::setIndexedTagName( $values, 'n' );
}
return $values;
@@ -476,7 +504,7 @@ class ApiPageSet extends ApiBase {
);
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'c' );
+ ApiResult::setIndexedTagName( $values, 'c' );
}
return $values;
@@ -513,7 +541,7 @@ class ApiPageSet extends ApiBase {
$values[] = $item;
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'i' );
+ ApiResult::setIndexedTagName( $values, 'i' );
}
return $values;
@@ -560,7 +588,7 @@ class ApiPageSet extends ApiBase {
}
/**
- * Get the list of revision IDs (requested with the revids= parameter)
+ * Get the list of valid revision IDs (requested with the revids= parameter)
* @return array Array of revID (int) => pageID (int)
*/
public function getRevisionIDs() {
@@ -568,6 +596,22 @@ class ApiPageSet extends ApiBase {
}
/**
+ * Get the list of non-deleted revision IDs (requested with the revids= parameter)
+ * @return array Array of revID (int) => pageID (int)
+ */
+ public function getLiveRevisionIDs() {
+ return $this->mLiveRevIDs;
+ }
+
+ /**
+ * Get the list of revision IDs that were associated with deleted titles.
+ * @return array Array of revID (int) => pageID (int)
+ */
+ public function getDeletedRevisionIDs() {
+ return $this->mDeletedRevIDs;
+ }
+
+ /**
* Revision IDs that were not found in the database
* @return array Array of revision IDs
*/
@@ -589,7 +633,7 @@ class ApiPageSet extends ApiBase {
);
}
if ( !empty( $values ) && $result ) {
- $result->setIndexedTagName( $values, 'rev' );
+ ApiResult::setIndexedTagName( $values, 'rev' );
}
return $values;
@@ -616,9 +660,7 @@ class ApiPageSet extends ApiBase {
* @param array $titles Array of Title objects
*/
public function populateFromTitles( $titles ) {
- $this->profileIn();
$this->initFromTitles( $titles );
- $this->profileOut();
}
/**
@@ -626,20 +668,20 @@ class ApiPageSet extends ApiBase {
* @param array $pageIDs Array of page IDs
*/
public function populateFromPageIDs( $pageIDs ) {
- $this->profileIn();
$this->initFromPageIds( $pageIDs );
- $this->profileOut();
}
/**
* Populate this PageSet from a rowset returned from the database
+ *
+ * Note that the query result must include the columns returned by
+ * $this->getPageTableFields().
+ *
* @param DatabaseBase $db
* @param ResultWrapper $queryResult Query result object
*/
public function populateFromQueryResult( $db, $queryResult ) {
- $this->profileIn();
$this->initFromQueryResult( $queryResult );
- $this->profileOut();
}
/**
@@ -647,9 +689,7 @@ class ApiPageSet extends ApiBase {
* @param array $revIDs Array of revision IDs
*/
public function populateFromRevisionIDs( $revIDs ) {
- $this->profileIn();
$this->initFromRevIDs( $revIDs );
- $this->profileOut();
}
/**
@@ -667,6 +707,8 @@ class ApiPageSet extends ApiBase {
if ( $this->mResolveRedirects && $row->page_is_redirect == '1' ) {
$this->mPendingRedirectIDs[$pageId] = $title;
} else {
+ $this->mGoodPages[$row->page_namespace][$row->page_title] = $pageId;
+ $this->mGoodAndMissingPages[$row->page_namespace][$row->page_title] = $pageId;
$this->mGoodTitles[$pageId] = $title;
}
@@ -710,10 +752,8 @@ class ApiPageSet extends ApiBase {
$set = $linkBatch->constructSet( 'page', $db );
// Get pageIDs data from the `page` table
- $this->profileDBIn();
$res = $db->select( 'page', $this->getPageTableFields(), $set,
__METHOD__ );
- $this->profileDBOut();
// Hack: get the ns:titles stored in array(ns => array(titles)) format
$this->initFromQueryResult( $res, $linkBatch->data, true ); // process Titles
@@ -744,10 +784,8 @@ class ApiPageSet extends ApiBase {
$db = $this->getDB();
// Get pageIDs data from the `page` table
- $this->profileDBIn();
$res = $db->select( 'page', $this->getPageTableFields(), $set,
__METHOD__ );
- $this->profileDBOut();
}
$this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
@@ -803,6 +841,8 @@ class ApiPageSet extends ApiBase {
foreach ( array_keys( $dbkeys ) as $dbkey ) {
$title = Title::makeTitle( $ns, $dbkey );
$this->mAllPages[$ns][$dbkey] = $this->mFakePageId;
+ $this->mMissingPages[$ns][$dbkey] = $this->mFakePageId;
+ $this->mGoodAndMissingPages[$ns][$dbkey] = $this->mFakePageId;
$this->mMissingTitles[$this->mFakePageId] = $title;
$this->mFakePageId--;
$this->mTitles[] = $title;
@@ -851,22 +891,64 @@ class ApiPageSet extends ApiBase {
$where = array( 'rev_id' => $revids, 'rev_page = page_id' );
// Get pageIDs data from the `page` table
- $this->profileDBIn();
$res = $db->select( $tables, $fields, $where, __METHOD__ );
foreach ( $res as $row ) {
$revid = intval( $row->rev_id );
$pageid = intval( $row->rev_page );
$this->mGoodRevIDs[$revid] = $pageid;
+ $this->mLiveRevIDs[$revid] = $pageid;
$pageids[$pageid] = '';
unset( $remaining[$revid] );
}
- $this->profileDBOut();
}
$this->mMissingRevIDs = array_keys( $remaining );
// Populate all the page information
$this->initFromPageIds( array_keys( $pageids ) );
+
+ // If the user can see deleted revisions, pull out the corresponding
+ // titles from the archive table and include them too. We ignore
+ // ar_page_id because deleted revisions are tied by title, not page_id.
+ if ( !empty( $this->mMissingRevIDs ) && $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $remaining = array_flip( $this->mMissingRevIDs );
+ $tables = array( 'archive' );
+ $fields = array( 'ar_rev_id', 'ar_namespace', 'ar_title' );
+ $where = array( 'ar_rev_id' => $this->mMissingRevIDs );
+
+ $res = $db->select( $tables, $fields, $where, __METHOD__ );
+ $titles = array();
+ foreach ( $res as $row ) {
+ $revid = intval( $row->ar_rev_id );
+ $titles[$revid] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ unset( $remaining[$revid] );
+ }
+
+ $this->initFromTitles( $titles );
+
+ foreach ( $titles as $revid => $title ) {
+ $ns = $title->getNamespace();
+ $dbkey = $title->getDBkey();
+
+ // Handle converted titles
+ if ( !isset( $this->mAllPages[$ns][$dbkey] ) &&
+ isset( $this->mConvertedTitles[$title->getPrefixedText()] )
+ ) {
+ $title = Title::newFromText( $this->mConvertedTitles[$title->getPrefixedText()] );
+ $ns = $title->getNamespace();
+ $dbkey = $title->getDBkey();
+ }
+
+ if ( isset( $this->mAllPages[$ns][$dbkey] ) ) {
+ $this->mGoodRevIDs[$revid] = $this->mAllPages[$ns][$dbkey];
+ $this->mDeletedRevIDs[$revid] = $this->mAllPages[$ns][$dbkey];
+ } else {
+ $remaining[$revid] = true;
+ }
+ }
+
+ $this->mMissingRevIDs = array_keys( $remaining );
+ }
}
/**
@@ -896,9 +978,7 @@ class ApiPageSet extends ApiBase {
}
// Get pageIDs data from the `page` table
- $this->profileDBIn();
$res = $db->select( 'page', $pageFlds, $set, __METHOD__ );
- $this->profileDBOut();
// Hack: get the ns:titles stored in array(ns => array(titles)) format
$this->initFromQueryResult( $res, $linkBatch->data, true );
@@ -917,7 +997,6 @@ class ApiPageSet extends ApiBase {
$lb = new LinkBatch();
$db = $this->getDB();
- $this->profileDBIn();
$res = $db->select(
'redirect',
array(
@@ -929,7 +1008,6 @@ class ApiPageSet extends ApiBase {
), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ),
__METHOD__
);
- $this->profileDBOut();
foreach ( $res as $row ) {
$rdfrom = intval( $row->rd_from );
$from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
@@ -940,7 +1018,9 @@ class ApiPageSet extends ApiBase {
$row->rd_interwiki
);
unset( $this->mPendingRedirectIDs[$rdfrom] );
- if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
+ if ( $to->isExternal() ) {
+ $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
+ } elseif ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
$lb->add( $row->rd_namespace, $row->rd_title );
}
$this->mRedirectTitles[$from] = $to;
@@ -1066,6 +1146,103 @@ class ApiPageSet extends ApiBase {
}
/**
+ * Set data for a title.
+ *
+ * This data may be extracted into an ApiResult using
+ * self::populateGeneratorData. This should generally be limited to
+ * data that is likely to be particularly useful to end users rather than
+ * just being a dump of everything returned in non-generator mode.
+ *
+ * Redirects here will *not* be followed, even if 'redirects' was
+ * specified, since in the case of multiple redirects we can't know which
+ * source's data to use on the target.
+ *
+ * @param Title $title
+ * @param array $data
+ */
+ public function setGeneratorData( Title $title, array $data ) {
+ $ns = $title->getNamespace();
+ $dbkey = $title->getDBkey();
+ $this->mGeneratorData[$ns][$dbkey] = $data;
+ }
+
+ /**
+ * Populate the generator data for all titles in the result
+ *
+ * The page data may be inserted into an ApiResult object or into an
+ * associative array. The $path parameter specifies the path within the
+ * ApiResult or array to find the "pages" node.
+ *
+ * The "pages" node itself must be an associative array mapping the page ID
+ * or fake page ID values returned by this pageset (see
+ * self::getAllTitlesByNamespace() and self::getSpecialTitles()) to
+ * associative arrays of page data. Each of those subarrays will have the
+ * data from self::setGeneratorData() merged in.
+ *
+ * Data that was set by self::setGeneratorData() for pages not in the
+ * "pages" node will be ignored.
+ *
+ * @param ApiResult|array &$result
+ * @param array $path
+ * @return bool Whether the data fit
+ */
+ public function populateGeneratorData( &$result, array $path = array() ) {
+ if ( $result instanceof ApiResult ) {
+ $data = $result->getResultData( $path );
+ if ( $data === null ) {
+ return true;
+ }
+ } else {
+ $data = &$result;
+ foreach ( $path as $key ) {
+ if ( !isset( $data[$key] ) ) {
+ // Path isn't in $result, so nothing to add, so everything
+ // "fits"
+ return true;
+ }
+ $data = &$data[$key];
+ }
+ }
+ foreach ( $this->mGeneratorData as $ns => $dbkeys ) {
+ if ( $ns === -1 ) {
+ $pages = array();
+ foreach ( $this->mSpecialTitles as $id => $title ) {
+ $pages[$title->getDBkey()] = $id;
+ }
+ } else {
+ if ( !isset( $this->mAllPages[$ns] ) ) {
+ // No known titles in the whole namespace. Skip it.
+ continue;
+ }
+ $pages = $this->mAllPages[$ns];
+ }
+ foreach ( $dbkeys as $dbkey => $genData ) {
+ if ( !isset( $pages[$dbkey] ) ) {
+ // Unknown title. Forget it.
+ continue;
+ }
+ $pageId = $pages[$dbkey];
+ if ( !isset( $data[$pageId] ) ) {
+ // $pageId didn't make it into the result. Ignore it.
+ continue;
+ }
+
+ if ( $result instanceof ApiResult ) {
+ $path2 = array_merge( $path, array( $pageId ) );
+ foreach ( $genData as $key => $value ) {
+ if ( !$result->addValue( $path2, $key, $value ) ) {
+ return false;
+ }
+ }
+ } else {
+ $data[$pageId] = array_merge( $data[$pageId], $genData );
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
* Get the database connection (read-only)
* @return DatabaseBase
*/
@@ -1095,26 +1272,51 @@ class ApiPageSet extends ApiBase {
public function getAllowedParams( $flags = 0 ) {
$result = array(
'titles' => array(
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_HELP_MSG => 'api-pageset-param-titles',
),
'pageids' => array(
ApiBase::PARAM_TYPE => 'integer',
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_HELP_MSG => 'api-pageset-param-pageids',
),
'revids' => array(
ApiBase::PARAM_TYPE => 'integer',
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_HELP_MSG => 'api-pageset-param-revids',
+ ),
+ 'generator' => array(
+ ApiBase::PARAM_TYPE => null,
+ ApiBase::PARAM_VALUE_LINKS => array(),
+ ApiBase::PARAM_HELP_MSG => 'api-pageset-param-generator',
+ ),
+ 'redirects' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => $this->mAllowGenerator
+ ? 'api-pageset-param-redirects-generator'
+ : 'api-pageset-param-redirects-nogenerator',
+ ),
+ 'converttitles' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => array(
+ 'api-pageset-param-converttitles',
+ new DeferredStringifier(
+ function ( IContextSource $context ) {
+ return $context->getLanguage()
+ ->commaList( LanguageConverter::$languagesWithVariants );
+ },
+ $this
+ )
+ ),
),
- 'redirects' => false,
- 'converttitles' => false,
);
- if ( $this->mAllowGenerator ) {
- if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
- $result['generator'] = array(
- ApiBase::PARAM_TYPE => $this->getGenerators()
- );
- } else {
- $result['generator'] = null;
+
+ if ( !$this->mAllowGenerator ) {
+ unset( $result['generator'] );
+ } elseif ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
+ foreach ( $this->getGenerators() as $g ) {
+ $result['generator'][ApiBase::PARAM_TYPE][] = $g;
+ $result['generator'][ApiBase::PARAM_VALUE_LINKS][$g] = "Special:ApiHelp/query+$g";
}
}
@@ -1148,23 +1350,4 @@ class ApiPageSet extends ApiBase {
return self::$generators;
}
-
- public function getParamDescription() {
- return array(
- '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'
- ),
- '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 )
- ),
- );
- }
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index 067b2f59..4dcaf78e 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -29,232 +29,329 @@
*/
class ApiParamInfo extends ApiBase {
- /**
- * @var ApiQuery
- */
- protected $queryObj;
+ private $helpFormat;
+ private $context;
public function __construct( ApiMain $main, $action ) {
parent::__construct( $main, $action );
- $this->queryObj = new ApiQuery( $this->getMain(), 'query' );
}
public function execute() {
// Get parameters
$params = $this->extractRequestParams();
- $resultObj = $this->getResult();
+
+ $this->helpFormat = $params['helpformat'];
+ $this->context = new RequestContext;
+ $this->context->setUser( new User ); // anon to avoid caching issues
+ $this->context->setLanguage( $this->getMain()->getLanguage() );
+
+ if ( is_array( $params['modules'] ) ) {
+ $modules = $params['modules'];
+ } else {
+ $modules = array();
+ }
+
+ if ( is_array( $params['querymodules'] ) ) {
+ $this->logFeatureUsage( 'action=paraminfo&querymodules' );
+ $queryModules = $params['querymodules'];
+ foreach ( $queryModules as $m ) {
+ $modules[] = 'query+' . $m;
+ }
+ } else {
+ $queryModules = array();
+ }
+
+ if ( is_array( $params['formatmodules'] ) ) {
+ $this->logFeatureUsage( 'action=paraminfo&formatmodules' );
+ $formatModules = $params['formatmodules'];
+ foreach ( $formatModules as $m ) {
+ $modules[] = $m;
+ }
+ } else {
+ $formatModules = array();
+ }
$res = array();
- $this->addModulesInfo( $params, 'modules', $res, $resultObj );
+ foreach ( $modules as $m ) {
+ try {
+ $module = $this->getModuleFromPath( $m );
+ } catch ( UsageException $ex ) {
+ $this->setWarning( $ex->getMessage() );
+ continue;
+ }
+ $key = 'modules';
+
+ // Back compat
+ $isBCQuery = false;
+ if ( $module->getParent() && $module->getParent()->getModuleName() == 'query' &&
+ in_array( $module->getModuleName(), $queryModules )
+ ) {
+ $isBCQuery = true;
+ $key = 'querymodules';
+ }
+ if ( in_array( $module->getModuleName(), $formatModules ) ) {
+ $key = 'formatmodules';
+ }
- $this->addModulesInfo( $params, 'querymodules', $res, $resultObj );
+ $item = $this->getModuleInfo( $module );
+ if ( $isBCQuery ) {
+ $item['querytype'] = $item['group'];
+ }
+ $res[$key][] = $item;
+ }
+
+ $result = $this->getResult();
+ $result->addValue( array( $this->getModuleName() ), 'helpformat', $this->helpFormat );
+
+ foreach ( $res as $key => $stuff ) {
+ ApiResult::setIndexedTagName( $res[$key], 'module' );
+ }
if ( $params['mainmodule'] ) {
- $res['mainmodule'] = $this->getClassInfo( $this->getMain() );
+ $this->logFeatureUsage( 'action=paraminfo&mainmodule' );
+ $res['mainmodule'] = $this->getModuleInfo( $this->getMain() );
}
if ( $params['pagesetmodule'] ) {
- $pageSet = new ApiPageSet( $this->queryObj );
- $res['pagesetmodule'] = $this->getClassInfo( $pageSet );
+ $this->logFeatureUsage( 'action=paraminfo&pagesetmodule' );
+ $pageSet = new ApiPageSet( $this->getMain()->getModuleManager()->getModule( 'query' ) );
+ $res['pagesetmodule'] = $this->getModuleInfo( $pageSet );
+ unset( $res['pagesetmodule']['name'] );
+ unset( $res['pagesetmodule']['path'] );
+ unset( $res['pagesetmodule']['group'] );
}
- $this->addModulesInfo( $params, 'formatmodules', $res, $resultObj );
-
- $resultObj->addValue( null, $this->getModuleName(), $res );
+ $result->addValue( null, $this->getModuleName(), $res );
}
/**
- * 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 $res Result array
+ * @param string $key Result key
+ * @param Message[] $msgs
+ * @param bool $joinLists
*/
- private function addModulesInfo( $params, $type, &$res, $resultObj ) {
- if ( !is_array( $params[$type] ) ) {
- return;
- }
- $isQuery = ( $type === 'querymodules' );
- if ( $isQuery ) {
- $mgr = $this->queryObj->getModuleManager();
- } else {
- $mgr = $this->getMain()->getModuleManager();
- }
- $res[$type] = array();
- foreach ( $params[$type] as $mod ) {
- if ( !$mgr->isDefined( $mod ) ) {
- $res[$type][] = array( 'name' => $mod, 'missing' => '' );
- continue;
- }
- $obj = $mgr->getModule( $mod );
- $item = $this->getClassInfo( $obj );
- $item['name'] = $mod;
- if ( $isQuery ) {
- $item['querytype'] = $mgr->getModuleGroup( $mod );
- }
- $res[$type][] = $item;
+ protected function formatHelpMessages( array &$res, $key, array $msgs, $joinLists = false ) {
+ switch ( $this->helpFormat ) {
+ case 'none':
+ break;
+
+ case 'wikitext':
+ $ret = array();
+ foreach ( $msgs as $m ) {
+ $ret[] = $m->setContext( $this->context )->text();
+ }
+ $res[$key] = join( "\n\n", $ret );
+ if ( $joinLists ) {
+ $res[$key] = preg_replace( '!^(([*#:;])[^\n]*)\n\n(?=\2)!m', "$1\n", $res[$key] );
+ }
+ break;
+
+ case 'html':
+ $ret = array();
+ foreach ( $msgs as $m ) {
+ $ret[] = $m->setContext( $this->context )->parseAsBlock();
+ }
+ $ret = join( "\n", $ret );
+ if ( $joinLists ) {
+ $ret = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $ret );
+ }
+ $res[$key] = Parser::stripOuterParagraph( $ret );
+ break;
+
+ case 'raw':
+ $res[$key] = array();
+ foreach ( $msgs as $m ) {
+ $a = array(
+ 'key' => $m->getKey(),
+ 'params' => $m->getParams(),
+ );
+ if ( $m instanceof ApiHelpParamValueMessage ) {
+ $a['forvalue'] = $m->getParamValue();
+ }
+ $res[$key][] = $a;
+ }
+ ApiResult::setIndexedTagName( $res[$key], 'msg' );
+ break;
}
- $resultObj->setIndexedTagName( $res[$type], 'module' );
}
/**
- * @param ApiBase $obj
+ * @param ApiBase $module
* @return ApiResult
*/
- private function getClassInfo( $obj ) {
+ private function getModuleInfo( $module ) {
$result = $this->getResult();
- $retval['classname'] = get_class( $obj );
- $retval['description'] = implode( "\n", (array)$obj->getFinalDescription() );
- $retval['examples'] = '';
-
- // version is deprecated since 1.21, but needs to be returned for v1
- $retval['version'] = '';
- $retval['prefix'] = $obj->getModulePrefix();
-
- if ( $obj->isReadMode() ) {
- $retval['readrights'] = '';
- }
- if ( $obj->isWriteMode() ) {
- $retval['writerights'] = '';
- }
- if ( $obj->mustBePosted() ) {
- $retval['mustbeposted'] = '';
- }
- if ( $obj instanceof ApiQueryGeneratorBase ) {
- $retval['generator'] = '';
+ $ret = array();
+ $path = $module->getModulePath();
+
+ $ret['name'] = $module->getModuleName();
+ $ret['classname'] = get_class( $module );
+ $ret['path'] = $path;
+ if ( !$module->isMain() ) {
+ $ret['group'] = $module->getParent()->getModuleManager()->getModuleGroup(
+ $module->getModuleName()
+ );
}
+ $ret['prefix'] = $module->getModulePrefix();
- $allowedParams = $obj->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
- if ( !is_array( $allowedParams ) ) {
- return $retval;
- }
+ $this->formatHelpMessages( $ret, 'description', $module->getFinalDescription() );
- $retval['helpurls'] = (array)$obj->getHelpUrls();
- if ( isset( $retval['helpurls'][0] ) && $retval['helpurls'][0] === false ) {
- $retval['helpurls'] = array();
+ foreach ( $module->getHelpFlags() as $flag ) {
+ $ret[$flag] = true;
}
- $result->setIndexedTagName( $retval['helpurls'], 'helpurl' );
- $examples = $obj->getExamples();
- $retval['allexamples'] = array();
- if ( $examples !== false ) {
- if ( is_string( $examples ) ) {
- $examples = array( $examples );
- }
- foreach ( $examples as $k => $v ) {
- if ( strlen( $retval['examples'] ) ) {
- $retval['examples'] .= ' ';
- }
- $item = array();
- if ( is_numeric( $k ) ) {
- $retval['examples'] .= $v;
- ApiResult::setContent( $item, $v );
- } else {
- if ( !is_array( $v ) ) {
- $item['description'] = $v;
+ $ret['helpurls'] = (array)$module->getHelpUrls();
+ if ( isset( $ret['helpurls'][0] ) && $ret['helpurls'][0] === false ) {
+ $ret['helpurls'] = array();
+ }
+ ApiResult::setIndexedTagName( $ret['helpurls'], 'helpurl' );
+
+ if ( $this->helpFormat !== 'none' ) {
+ $ret['examples'] = array();
+ $examples = $module->getExamplesMessages();
+ foreach ( $examples as $qs => $msg ) {
+ $item = array(
+ 'query' => $qs
+ );
+ $msg = ApiBase::makeMessage( $msg, $this->context, array(
+ $module->getModulePrefix(),
+ $module->getModuleName(),
+ $module->getModulePath()
+ ) );
+ $this->formatHelpMessages( $item, 'description', array( $msg ) );
+ if ( isset( $item['description'] ) ) {
+ if ( is_array( $item['description'] ) ) {
+ $item['description'] = $item['description'][0];
} else {
- $item['description'] = implode( $v, "\n" );
+ ApiResult::setSubelementsList( $item, 'description' );
}
- $retval['examples'] .= $item['description'] . ' ' . $k;
- ApiResult::setContent( $item, $k );
}
- $retval['allexamples'][] = $item;
+ $ret['examples'][] = $item;
}
+ ApiResult::setIndexedTagName( $ret['examples'], 'example' );
}
- $result->setIndexedTagName( $retval['allexamples'], 'example' );
-
- $retval['parameters'] = array();
- $paramDesc = $obj->getFinalParamDescription();
- foreach ( $allowedParams as $n => $p ) {
- $a = array( 'name' => $n );
- if ( isset( $paramDesc[$n] ) ) {
- $a['description'] = implode( "\n", (array)$paramDesc[$n] );
- }
- //handle shorthand
- if ( !is_array( $p ) ) {
- $p = array(
- ApiBase::PARAM_DFLT => $p,
- );
+ $ret['parameters'] = array();
+ $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+ $paramDesc = $module->getFinalParamDescription();
+ foreach ( $params as $name => $settings ) {
+ if ( !is_array( $settings ) ) {
+ $settings = array( ApiBase::PARAM_DFLT => $settings );
}
- //handle missing type
- if ( !isset( $p[ApiBase::PARAM_TYPE] ) ) {
- $dflt = isset( $p[ApiBase::PARAM_DFLT] ) ? $p[ApiBase::PARAM_DFLT] : null;
- if ( is_bool( $dflt ) ) {
- $p[ApiBase::PARAM_TYPE] = 'boolean';
- } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
- $p[ApiBase::PARAM_TYPE] = 'string';
- } elseif ( is_int( $dflt ) ) {
- $p[ApiBase::PARAM_TYPE] = 'integer';
- }
+ $item = array(
+ 'name' => $name
+ );
+ if ( isset( $paramDesc[$name] ) ) {
+ $this->formatHelpMessages( $item, 'description', $paramDesc[$name], true );
}
- if ( isset( $p[ApiBase::PARAM_DEPRECATED] ) && $p[ApiBase::PARAM_DEPRECATED] ) {
- $a['deprecated'] = '';
+ $item['required'] = !empty( $settings[ApiBase::PARAM_REQUIRED] );
+
+ if ( !empty( $settings[ApiBase::PARAM_DEPRECATED] ) ) {
+ $item['deprecated'] = true;
}
- if ( isset( $p[ApiBase::PARAM_REQUIRED] ) && $p[ApiBase::PARAM_REQUIRED] ) {
- $a['required'] = '';
+
+ if ( $name === 'token' && $module->needsToken() ) {
+ $item['tokentype'] = $module->needsToken();
}
- if ( $n === 'token' && $obj->needsToken() ) {
- $a['tokentype'] = $obj->needsToken();
+ if ( !isset( $settings[ApiBase::PARAM_TYPE] ) ) {
+ $dflt = isset( $settings[ApiBase::PARAM_DFLT] )
+ ? $settings[ApiBase::PARAM_DFLT]
+ : null;
+ if ( is_bool( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'boolean';
+ } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'string';
+ } elseif ( is_int( $dflt ) ) {
+ $settings[ApiBase::PARAM_TYPE] = 'integer';
+ }
}
- if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
- $type = $p[ApiBase::PARAM_TYPE];
- if ( $type === 'boolean' ) {
- $a['default'] = ( $p[ApiBase::PARAM_DFLT] ? 'true' : 'false' );
- } elseif ( $type === 'string' ) {
- $a['default'] = strval( $p[ApiBase::PARAM_DFLT] );
- } elseif ( $type === 'integer' ) {
- $a['default'] = intval( $p[ApiBase::PARAM_DFLT] );
- } else {
- $a['default'] = $p[ApiBase::PARAM_DFLT];
+ if ( isset( $settings[ApiBase::PARAM_DFLT] ) ) {
+ switch ( $settings[ApiBase::PARAM_TYPE] ) {
+ case 'boolean':
+ $item['default'] = ( $settings[ApiBase::PARAM_DFLT] ? 'true' : 'false' );
+ break;
+ case 'string':
+ $item['default'] = strval( $settings[ApiBase::PARAM_DFLT] );
+ break;
+ case 'integer':
+ $item['default'] = intval( $settings[ApiBase::PARAM_DFLT] );
+ break;
+ default:
+ $item['default'] = $settings[ApiBase::PARAM_DFLT];
+ break;
}
}
- if ( isset( $p[ApiBase::PARAM_ISMULTI] ) && $p[ApiBase::PARAM_ISMULTI] ) {
- $a['multi'] = '';
- $a['limit'] = $this->getMain()->canApiHighLimits() ?
+
+ $item['multi'] = !empty( $settings[ApiBase::PARAM_ISMULTI] );
+ if ( $item['multi'] ) {
+ $item['limit'] = $this->getMain()->canApiHighLimits() ?
ApiBase::LIMIT_SML2 :
ApiBase::LIMIT_SML1;
- $a['lowlimit'] = ApiBase::LIMIT_SML1;
- $a['highlimit'] = ApiBase::LIMIT_SML2;
+ $item['lowlimit'] = ApiBase::LIMIT_SML1;
+ $item['highlimit'] = ApiBase::LIMIT_SML2;
}
- if ( isset( $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) && $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) {
- $a['allowsduplicates'] = '';
+ if ( !empty( $settings[ApiBase::PARAM_ALLOW_DUPLICATES] ) ) {
+ $item['allowsduplicates'] = true;
}
- if ( isset( $p[ApiBase::PARAM_TYPE] ) ) {
- if ( $p[ApiBase::PARAM_TYPE] === 'submodule' ) {
- $a['type'] = $obj->getModuleManager()->getNames( $n );
- sort( $a['type'] );
- $a['submodules'] = '';
+ if ( isset( $settings[ApiBase::PARAM_TYPE] ) ) {
+ if ( $settings[ApiBase::PARAM_TYPE] === 'submodule' ) {
+ $item['type'] = $module->getModuleManager()->getNames( $name );
+ sort( $item['type'] );
+ $item['submodules'] = true;
} else {
- $a['type'] = $p[ApiBase::PARAM_TYPE];
+ $item['type'] = $settings[ApiBase::PARAM_TYPE];
}
- if ( is_array( $a['type'] ) ) {
+ if ( is_array( $item['type'] ) ) {
// To prevent sparse arrays from being serialized to JSON as objects
- $a['type'] = array_values( $a['type'] );
- $result->setIndexedTagName( $a['type'], 't' );
+ $item['type'] = array_values( $item['type'] );
+ ApiResult::setIndexedTagName( $item['type'], 't' );
}
}
- if ( isset( $p[ApiBase::PARAM_MAX] ) ) {
- $a['max'] = $p[ApiBase::PARAM_MAX];
+ if ( isset( $settings[ApiBase::PARAM_MAX] ) ) {
+ $item['max'] = $settings[ApiBase::PARAM_MAX];
+ }
+ if ( isset( $settings[ApiBase::PARAM_MAX2] ) ) {
+ $item['highmax'] = $settings[ApiBase::PARAM_MAX2];
}
- if ( isset( $p[ApiBase::PARAM_MAX2] ) ) {
- $a['highmax'] = $p[ApiBase::PARAM_MAX2];
+ if ( isset( $settings[ApiBase::PARAM_MIN] ) ) {
+ $item['min'] = $settings[ApiBase::PARAM_MIN];
}
- if ( isset( $p[ApiBase::PARAM_MIN] ) ) {
- $a['min'] = $p[ApiBase::PARAM_MIN];
+
+ if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
+ $item['info'] = array();
+ foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
+ $tag = array_shift( $i );
+ $info = array(
+ 'name' => $tag,
+ );
+ if ( count( $i ) ) {
+ $info['values'] = $i;
+ ApiResult::setIndexedTagName( $info['values'], 'v' );
+ }
+ $this->formatHelpMessages( $info, 'text', array(
+ $this->context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
+ ->numParams( count( $i ) )
+ ->params( $this->context->getLanguage()->commaList( $i ) )
+ ->params( $module->getModulePrefix() )
+ ) );
+ ApiResult::setSubelementsList( $info, 'text' );
+ $item['info'][] = $info;
+ }
+ ApiResult::setIndexedTagName( $item['info'], 'i' );
}
- $retval['parameters'][] = $a;
+
+ $ret['parameters'][] = $item;
}
- $result->setIndexedTagName( $retval['parameters'], 'param' );
+ ApiResult::setIndexedTagName( $ret['parameters'], 'param' );
- return $retval;
+ return $ret;
}
public function isReadMode() {
@@ -262,9 +359,9 @@ class ApiParamInfo extends ApiBase {
}
public function getAllowedParams() {
- $modules = $this->getMain()->getModuleManager()->getNames( 'action' );
- sort( $modules );
- $querymodules = $this->queryObj->getModuleManager()->getNames();
+ // back compat
+ $querymodules = $this->getMain()->getModuleManager()
+ ->getModule( 'query' )->getModuleManager()->getNames();
sort( $querymodules );
$formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
sort( $formatmodules );
@@ -272,39 +369,35 @@ class ApiParamInfo extends ApiBase {
return array(
'modules' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $modules,
),
+ 'helpformat' => array(
+ ApiBase::PARAM_DFLT => 'none',
+ ApiBase::PARAM_TYPE => array( 'html', 'wikitext', 'raw', 'none' ),
+ ),
+
'querymodules' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => $querymodules,
),
- 'mainmodule' => false,
- 'pagesetmodule' => false,
+ 'mainmodule' => array(
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
+ 'pagesetmodule' => array(
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
'formatmodules' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => $formatmodules,
)
);
}
- public function getParamDescription() {
- return array(
- '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',
- 'formatmodules' => 'List of format module names (value of format= parameter)',
- );
- }
-
- public function getDescription() {
- return 'Obtain information about certain API parameters and errors.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=paraminfo&modules=parse&querymodules=allpages|siteinfo'
+ 'action=paraminfo&modules=parse|phpfm|query+allpages|query+siteinfo'
+ => 'apihelp-paraminfo-example-1',
);
}
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index 06fdf85b..1b5e2658 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -70,6 +70,9 @@ class ApiParse extends ApiBase {
if ( isset( $params['section'] ) ) {
$this->section = $params['section'];
+ if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
+ $this->dieUsage( "The section parameter must be a valid section id or 'new'", "invalidsection" );
+ }
} else {
$this->section = false;
}
@@ -79,25 +82,18 @@ 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
- $oldLang = null;
- if ( isset( $params['uselang'] )
- && $params['uselang'] != $this->getContext()->getLanguage()->getCode()
- ) {
- $oldLang = $this->getContext()->getLanguage(); // Backup language
- $this->getContext()->setLanguage( Language::factory( $params['uselang'] ) );
- }
-
$redirValues = null;
// Return result
$result = $this->getResult();
if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
+ if ( $this->section === 'new' ) {
+ $this->dieUsage( 'section=new cannot be combined with oldid, pageid or page parameters. Please use text', 'params' );
+ }
if ( !is_null( $oldid ) ) {
// Don't use the parser cache
- $rev = Revision::newFromID( $oldid );
+ $rev = Revision::newFromId( $oldid );
if ( !$rev ) {
$this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
}
@@ -128,7 +124,6 @@ class ApiParse extends ApiBase {
} else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
$reqParams = array(
- 'action' => 'query',
'redirects' => '',
);
if ( !is_null( $pageid ) ) {
@@ -138,14 +133,13 @@ class ApiParse extends ApiBase {
}
$req = new FauxRequest( $reqParams );
$main = new ApiMain( $req );
- $main->execute();
- $data = $main->getResultData();
- $redirValues = isset( $data['query']['redirects'] )
- ? $data['query']['redirects']
- : array();
+ $pageSet = new ApiPageSet( $main );
+ $pageSet->execute();
+ $redirValues = $pageSet->getRedirectTitlesAsResult( $this->getResult() );
+
$to = $page;
- foreach ( (array)$redirValues as $r ) {
- $to = $r['to'];
+ foreach ( $pageSet->getRedirectTitles() as $title ) {
+ $to = $title->getFullText();
}
$pageParams = array( 'title' => $to );
} elseif ( !is_null( $pageid ) ) {
@@ -213,7 +207,14 @@ class ApiParse extends ApiBase {
}
if ( $this->section !== false ) {
- $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
+ if ( $this->section === 'new' ) {
+ // Insert the section title above the content.
+ if ( !is_null( $params['sectiontitle'] ) && $params['sectiontitle'] !== '' ) {
+ $this->content = $this->content->addSectionHeader( $params['sectiontitle'] );
+ }
+ } else {
+ $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
+ }
}
if ( $params['pst'] || $params['onlypst'] ) {
@@ -222,12 +223,19 @@ class ApiParse extends ApiBase {
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array = array();
- $result_array['text'] = array();
- ApiResult::setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
+ $result_array['text'] = $this->pstContent->serialize( $format );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
if ( isset( $prop['wikitext'] ) ) {
- $result_array['wikitext'] = array();
- ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ $result_array['wikitext'] = $this->content->serialize( $format );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
}
+ if ( !is_null( $params['summary'] ) ||
+ ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
+ ) {
+ $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
+ }
+
$result->addValue( null, $this->getModuleName(), $result_array );
return;
@@ -258,16 +266,15 @@ class ApiParse extends ApiBase {
}
if ( isset( $prop['text'] ) ) {
- $result_array['text'] = array();
- ApiResult::setContent( $result_array['text'], $p_result->getText() );
+ $result_array['text'] = $p_result->getText();
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
}
- if ( !is_null( $params['summary'] ) ) {
- $result_array['parsedsummary'] = array();
- ApiResult::setContent(
- $result_array['parsedsummary'],
- Linker::formatComment( $params['summary'], $titleObj )
- );
+ if ( !is_null( $params['summary'] ) ||
+ ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
+ ) {
+ $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
}
if ( isset( $prop['langlinks'] ) ) {
@@ -277,7 +284,7 @@ class ApiParse extends ApiBase {
// Link flags are ignored for now, but may in the future be
// included in the result.
$linkFlags = array();
- wfRunHooks( 'LanguageLinks', array( $titleObj, &$langlinks, &$linkFlags ) );
+ Hooks::run( 'LanguageLinks', array( $titleObj, &$langlinks, &$linkFlags ) );
}
} else {
$langlinks = false;
@@ -290,9 +297,8 @@ class ApiParse extends ApiBase {
$result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
}
if ( isset( $prop['categorieshtml'] ) ) {
- $categoriesHtml = $this->categoriesHtml( $p_result->getCategories() );
- $result_array['categorieshtml'] = array();
- ApiResult::setContent( $result_array['categorieshtml'], $categoriesHtml );
+ $result_array['categorieshtml'] = $this->categoriesHtml( $p_result->getCategories() );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
}
if ( isset( $prop['links'] ) ) {
$result_array['links'] = $this->formatLinks( $p_result->getLinks() );
@@ -332,11 +338,8 @@ class ApiParse extends ApiBase {
}
if ( isset( $prop['headhtml'] ) ) {
- $result_array['headhtml'] = array();
- ApiResult::setContent(
- $result_array['headhtml'],
- $context->getOutput()->headElement( $context->getSkin() )
- );
+ $result_array['headhtml'] = $context->getOutput()->headElement( $context->getSkin() );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
}
}
@@ -347,20 +350,26 @@ class ApiParse extends ApiBase {
$result_array['modulemessages'] = array_values( array_unique( $p_result->getModuleMessages() ) );
}
+ if ( isset( $prop['indicators'] ) ) {
+ $result_array['indicators'] = (array)$p_result->getIndicators();
+ ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
+ }
+
if ( isset( $prop['iwlinks'] ) ) {
$result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
}
if ( isset( $prop['wikitext'] ) ) {
- $result_array['wikitext'] = array();
- ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ $result_array['wikitext'] = $this->content->serialize( $format );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
if ( !is_null( $this->pstContent ) ) {
- $result_array['psttext'] = array();
- ApiResult::setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
+ $result_array['psttext'] = $this->pstContent->serialize( $format );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
}
}
if ( isset( $prop['properties'] ) ) {
- $result_array['properties'] = $this->formatProperties( $p_result->getProperties() );
+ $result_array['properties'] = (array)$p_result->getProperties();
+ ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
}
if ( isset( $prop['limitreportdata'] ) ) {
@@ -368,9 +377,8 @@ class ApiParse extends ApiBase {
$this->formatLimitReportData( $p_result->getLimitReportData() );
}
if ( isset( $prop['limitreporthtml'] ) ) {
- $limitreportHtml = EditPage::getPreviewLimitReport( $p_result );
- $result_array['limitreporthtml'] = array();
- ApiResult::setContent( $result_array['limitreporthtml'], $limitreportHtml );
+ $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
}
if ( $params['generatexml'] ) {
@@ -378,15 +386,15 @@ class ApiParse extends ApiBase {
$this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
}
- $wgParser->startExternalParse( $titleObj, $popts, OT_PREPROCESS );
+ $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
} else {
$xml = $dom->__toString();
}
- $result_array['parsetree'] = array();
- ApiResult::setContent( $result_array['parsetree'], $xml );
+ $result_array['parsetree'] = $xml;
+ $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
}
$result_mapping = array(
@@ -401,6 +409,7 @@ class ApiParse extends ApiBase {
'sections' => 's',
'headitems' => 'hi',
'modules' => 'm',
+ 'indicators' => 'ind',
'modulescripts' => 'm',
'modulestyles' => 'm',
'modulemessages' => 'm',
@@ -409,10 +418,6 @@ class ApiParse extends ApiBase {
);
$this->setIndexedTagNames( $result_array, $result_mapping );
$result->addValue( null, $this->getModuleName(), $result_array );
-
- if ( !is_null( $oldLang ) ) {
- $this->getContext()->setLanguage( $oldLang ); // Reset language to $oldLang
- }
}
/**
@@ -424,7 +429,6 @@ class ApiParse extends ApiBase {
* @return ParserOptions
*/
protected function makeParserOptions( WikiPage $pageObj, array $params ) {
- wfProfileIn( __METHOD__ );
$popts = $pageObj->makeParserOptions( $this->getContext() );
$popts->enableLimitReport( !$params['disablepp'] );
@@ -432,8 +436,6 @@ class ApiParse extends ApiBase {
$popts->setIsSectionPreview( $params['sectionpreview'] );
$popts->setEditSection( !$params['disableeditsection'] );
- wfProfileOut( __METHOD__ );
-
return $popts;
}
@@ -489,6 +491,30 @@ class ApiParse extends ApiBase {
return $section;
}
+ /**
+ * This mimicks the behavior of EditPage in formatting a summary
+ *
+ * @param Title $title of the page being parsed
+ * @param Array $params the API parameters of the request
+ * @return Content|bool
+ */
+ private function formatSummary( $title, $params ) {
+ global $wgParser;
+ $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
+ $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
+
+ if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
+ if( $sectionTitle !== '' ) {
+ $summary = $params['sectiontitle'];
+ }
+ if ( $summary !== '' ) {
+ $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
+ ->inContentLanguage()->text();
+ }
+ }
+ return Linker::formatComment( $summary, $title, $this->section === 'new' );
+ }
+
private function formatLangLinks( $links ) {
$result = array();
foreach ( $links as $link ) {
@@ -499,7 +525,7 @@ 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=)
+ // localised language name in 'uselang' language
$entry['langname'] = Language::fetchLanguageName(
$title->getInterwiki(),
$this->getLanguage()->getCode()
@@ -508,7 +534,7 @@ class ApiParse extends ApiBase {
// native language name
$entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
}
- ApiResult::setContent( $entry, $bits[1] );
+ ApiResult::setContentValue( $entry, 'title', $bits[1] );
$result[] = $entry;
}
@@ -543,11 +569,11 @@ class ApiParse extends ApiBase {
foreach ( $links as $link => $sortkey ) {
$entry = array();
$entry['sortkey'] = $sortkey;
- ApiResult::setContent( $entry, $link );
+ ApiResult::setContentValue( $entry, 'category', $link );
if ( !isset( $hiddencats[$link] ) ) {
- $entry['missing'] = '';
+ $entry['missing'] = true;
} elseif ( $hiddencats[$link] ) {
- $entry['hidden'] = '';
+ $entry['hidden'] = true;
}
$result[] = $entry;
}
@@ -568,10 +594,8 @@ class ApiParse extends ApiBase {
foreach ( $nslinks as $title => $id ) {
$entry = array();
$entry['ns'] = $ns;
- ApiResult::setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() );
- if ( $id != 0 ) {
- $entry['exists'] = '';
- }
+ ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
+ $entry['exists'] = $id != 0;
$result[] = $entry;
}
}
@@ -591,7 +615,7 @@ class ApiParse extends ApiBase {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
- ApiResult::setContent( $entry, $title->getFullText() );
+ ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
$result[] = $entry;
}
}
@@ -604,19 +628,7 @@ class ApiParse extends ApiBase {
foreach ( $headItems as $tag => $content ) {
$entry = array();
$entry['tag'] = $tag;
- ApiResult::setContent( $entry, $content );
- $result[] = $entry;
- }
-
- return $result;
- }
-
- private function formatProperties( $properties ) {
- $result = array();
- foreach ( $properties as $name => $value ) {
- $entry = array();
- $entry['name'] = $name;
- ApiResult::setContent( $entry, $value );
+ ApiResult::setContentValue( $entry, 'content', $content );
$result[] = $entry;
}
@@ -628,7 +640,7 @@ class ApiParse extends ApiBase {
foreach ( $css as $file => $link ) {
$entry = array();
$entry['file'] = $file;
- ApiResult::setContent( $entry, $link );
+ ApiResult::setContentValue( $entry, 'link', $link );
$result[] = $entry;
}
@@ -645,8 +657,7 @@ class ApiParse extends ApiBase {
if ( !is_array( $value ) ) {
$value = array( $value );
}
- $apiResult->setIndexedTagName( $value, 'param' );
- $apiResult->setIndexedTagName_recursive( $value, 'param' );
+ ApiResult::setIndexedTagNameRecursive( $value, 'param' );
$entry = array_merge( $entry, $value );
$result[] = $entry;
}
@@ -657,7 +668,7 @@ class ApiParse extends ApiBase {
private function setIndexedTagNames( &$array, $mapping ) {
foreach ( $mapping as $key => $name ) {
if ( isset( $array[$key] ) ) {
- $this->getResult()->setIndexedTagName( $array[$key], $name );
+ ApiResult::setIndexedTagName( $array[$key], $name );
}
}
}
@@ -694,6 +705,7 @@ class ApiParse extends ApiBase {
'headitems',
'headhtml',
'modules',
+ 'indicators',
'iwlinks',
'wikitext',
'properties',
@@ -704,11 +716,18 @@ class ApiParse extends ApiBase {
'pst' => false,
'onlypst' => false,
'effectivelanglinks' => false,
- 'uselang' => null,
'section' => null,
+ 'sectiontitle' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
'disablepp' => false,
'disableeditsection' => false,
- 'generatexml' => false,
+ 'generatexml' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => array(
+ 'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
+ ),
+ ),
'preview' => false,
'sectionpreview' => false,
'disabletoc' => false,
@@ -721,97 +740,16 @@ 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, {$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",
- 'prop' => array(
- 'Which pieces of information to get',
- ' text - Gives the parsed text of the wikitext',
- ' 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',
- ' 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',
- ' externallinks - Gives the external links in the parsed wikitext',
- ' sections - Gives the sections in the parsed wikitext',
- ' revid - Adds the revision ID of the parsed page',
- ' 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)',
- ),
- 'pst' => array(
- 'Do a pre-save transform on the input before parsing it',
- "Only valid when used with {$p}text",
- ),
- 'onlypst' => array(
- 'Do a pre-save transform (PST) on the input, but don\'t parse it',
- 'Returns the same wikitext, after a PST has been applied.',
- "Only valid when used with {$p}text",
- ),
- '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. 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",
- ),
- );
- }
-
- 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.',
- '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.",
- "3) Specify only a summary to parse. {$p}prop should be given an empty value.",
- );
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=parse&page=Project:Sandbox' => 'Parse a page',
- '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',
+ 'action=parse&page=Project:Sandbox'
+ => 'apihelp-parse-example-page',
+ 'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
+ => 'apihelp-parse-example-text',
+ 'action=parse&text={{PAGENAME}}&title=Test'
+ => 'apihelp-parse-example-texttitle',
+ 'action=parse&summary=Some+[[link]]&prop='
+ => 'apihelp-parse-example-summary',
);
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index 8b66781a..779c4188 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -38,7 +38,7 @@ class ApiPatrol extends ApiBase {
$this->requireOnlyOneParameter( $params, 'rcid', 'revid' );
if ( isset( $params['rcid'] ) ) {
- $rc = RecentChange::newFromID( $params['rcid'] );
+ $rc = RecentChange::newFromId( $params['rcid'] );
if ( !$rc ) {
$this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) );
}
@@ -86,25 +86,16 @@ class ApiPatrol extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'rcid' => 'Recentchanges ID to patrol',
- 'revid' => 'Revision ID to patrol',
- );
- }
-
- public function getDescription() {
- return 'Patrol a page or revision.';
- }
-
public function needsToken() {
return 'patrol';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=patrol&token=123ABC&rcid=230672766',
- 'api.php?action=patrol&token=123ABC&revid=230672766'
+ 'action=patrol&token=123ABC&rcid=230672766'
+ => 'apihelp-patrol-example-rcid',
+ 'action=patrol&token=123ABC&revid=230672766'
+ => 'apihelp-patrol-example-revid',
);
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index a3d12b7f..ad028375 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -77,7 +77,7 @@ class ApiProtect extends ApiBase {
$this->dieUsageMsg( array( 'protect-invalidlevel', $p[1] ) );
}
- if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'infinity', 'never' ) ) ) {
+ if ( wfIsInfinity( $expiry[$i] ) ) {
$expiryarray[$p[0]] = $db->getInfinity();
} else {
$exp = strtotime( $expiry[$i] );
@@ -124,11 +124,11 @@ class ApiProtect extends ApiBase {
'reason' => $params['reason']
);
if ( $cascade ) {
- $res['cascade'] = '';
+ $res['cascade'] = true;
}
$res['protections'] = $resultProtections;
$result = $this->getResult();
- $result->setIndexedTagName( $res['protections'], 'protection' );
+ ApiResult::setIndexedTagName( $res['protections'], 'protection' );
$result->addValue( null, $this->getModuleName(), $res );
}
@@ -175,43 +175,21 @@ 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",
- '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\', \'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\''
- ),
- '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 getDescription() {
- return 'Change the protection level of a page.';
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
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&' .
+ 'action=protect&title=Main%20Page&token=123ABC&' .
+ 'protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never'
+ => 'apihelp-protect-example-protect',
+ 'action=protect&title=Main%20Page&token=123ABC&' .
'protections=edit=all|move=all&reason=Lifting%20restrictions'
+ => 'apihelp-protect-example-unprotect',
+ 'action=protect&title=Main%20Page&token=123ABC&' .
+ 'protections=&reason=Lifting%20restrictions'
+ => 'apihelp-protect-example-unprotect2',
);
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index 7667b235..a22be498 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -38,7 +38,8 @@ class ApiPurge extends ApiBase {
public function execute() {
$params = $this->extractRequestParams();
- $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+ $continuationManager = new ApiContinuationManager( $this, array(), array() );
+ $this->setContinuationManager( $continuationManager );
$forceLinkUpdate = $params['forcelinkupdate'];
$forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate'];
@@ -52,7 +53,7 @@ class ApiPurge extends ApiBase {
ApiQueryBase::addTitleInfo( $r, $title );
$page = WikiPage::factory( $title );
$page->doPurge(); // Directly purge and skip the UI part of purge().
- $r['purged'] = '';
+ $r['purged'] = true;
if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) {
if ( !$this->getUser()->pingLimiter( 'linkpurge' ) ) {
@@ -73,7 +74,7 @@ class ApiPurge extends ApiBase {
$title, null, $forceRecursiveLinkUpdate, $p_result );
DataUpdate::runUpdates( $updates );
- $r['linkupdate'] = '';
+ $r['linkupdate'] = true;
if ( $enableParserCache ) {
$pcache = ParserCache::singleton();
@@ -89,7 +90,7 @@ class ApiPurge extends ApiBase {
$result[] = $r;
}
$apiResult = $this->getResult();
- $apiResult->setIndexedTagName( $result, 'page' );
+ ApiResult::setIndexedTagName( $result, 'page' );
$apiResult->addValue( null, $this->getModuleName(), $result );
$values = $pageSet->getNormalizedTitlesAsResult( $apiResult );
@@ -105,7 +106,8 @@ class ApiPurge extends ApiBase {
$apiResult->addValue( null, 'redirects', $values );
}
- $apiResult->endContinuation();
+ $this->setContinuationManager( null );
+ $continuationManager->setContinuationIntoResult( $apiResult );
}
/**
@@ -133,7 +135,9 @@ class ApiPurge extends ApiBase {
$result = array(
'forcelinkupdate' => false,
'forcerecursivelinkupdate' => false,
- 'continue' => '',
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
@@ -142,25 +146,12 @@ class ApiPurge extends ApiBase {
return $result;
}
- public function getParamDescription() {
- return $this->getPageSet()->getFinalParamDescription()
- + array(
- '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 getDescription() {
- return array( 'Purge the cache for the given titles.',
- 'Requires a POST request if the user is not logged in.'
- );
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=purge&titles=Main_Page|API' => 'Purge the "Main Page" and the "API" page',
+ 'action=purge&titles=Main_Page|API'
+ => 'apihelp-purge-example-simple',
+ 'action=purge&generator=allpages&gapnamespace=0&gaplimit=10'
+ => 'apihelp-purge-example-generator',
);
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index 7c750e41..bfe32052 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -45,6 +45,7 @@ class ApiQuery extends ApiBase {
'categories' => 'ApiQueryCategories',
'categoryinfo' => 'ApiQueryCategoryInfo',
'contributors' => 'ApiQueryContributors',
+ 'deletedrevisions' => 'ApiQueryDeletedRevisions',
'duplicatefiles' => 'ApiQueryDuplicateFiles',
'extlinks' => 'ApiQueryExternalLinks',
'fileusage' => 'ApiQueryBacklinksprop',
@@ -69,6 +70,7 @@ class ApiQuery extends ApiBase {
*/
private static $QueryListModules = array(
'allcategories' => 'ApiQueryAllCategories',
+ 'alldeletedrevisions' => 'ApiQueryAllDeletedRevisions',
'allfileusages' => 'ApiQueryAllLinks',
'allimages' => 'ApiQueryAllImages',
'alllinks' => 'ApiQueryAllLinks',
@@ -141,6 +143,8 @@ class ApiQuery extends ApiBase {
$this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
$this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' );
+ Hooks::run( 'ApiQuery::moduleManager', array( $this->mModuleMgr ) );
+
// Create PageSet that will process titles/pageids/revids/generator
$this->mPageSet = new ApiPageSet( $this );
}
@@ -165,9 +169,7 @@ class ApiQuery extends ApiBase {
*/
public function getNamedDB( $name, $db, $groups ) {
if ( !array_key_exists( $name, $this->mNamedDB ) ) {
- $this->profileDBIn();
$this->mNamedDB[$name] = wfGetDB( $db, $groups );
- $this->profileDBOut();
}
return $this->mNamedDB[$name];
@@ -255,11 +257,11 @@ class ApiQuery extends ApiBase {
$this->instantiateModules( $allModules, 'meta' );
// Filter modules based on continue parameter
- list( $generatorDone, $modules ) = $this->getResult()->beginContinuation(
- $this->mParams['continue'], $allModules, $propModules
- );
+ $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
+ $this->setContinuationManager( $continuationManager );
+ $modules = $continuationManager->getRunModules();
- if ( !$generatorDone ) {
+ if ( !$continuationManager->isGeneratorDone() ) {
// Query modules may optimize data requests through the $this->getPageSet()
// object by adding extra fields from the page table.
foreach ( $modules as $module ) {
@@ -281,19 +283,36 @@ class ApiQuery extends ApiBase {
$params = $module->extractRequestParams();
$cacheMode = $this->mergeCacheMode(
$cacheMode, $module->getCacheMode( $params ) );
- $module->profileIn();
$module->execute();
- wfRunHooks( 'APIQueryAfterExecute', array( &$module ) );
- $module->profileOut();
+ Hooks::run( 'APIQueryAfterExecute', array( &$module ) );
}
// Set the cache mode
$this->getMain()->setCacheMode( $cacheMode );
// Write the continuation data into the result
- $this->getResult()->endContinuation(
- $this->mParams['continue'] === null ? 'raw' : 'standard'
- );
+ $this->setContinuationManager( null );
+ if ( $this->mParams['continue'] === null ) {
+ $data = $continuationManager->getRawContinuation();
+ if ( $data ) {
+ $this->getResult()->addValue( null, 'query-continue', $data,
+ ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+ } else {
+ $continuationManager->setContinuationIntoResult( $this->getResult() );
+ }
+
+ if ( $this->mParams['continue'] === null && !$this->mParams['rawcontinue'] &&
+ $this->getResult()->getResultData( 'query-continue' ) !== null
+ ) {
+ $this->logFeatureUsage( 'action=query&!rawcontinue&!continue' );
+ $this->setWarning(
+ 'Formatting of continuation data will be changing soon. ' .
+ 'To continue using the current formatting, use the \'rawcontinue\' parameter. ' .
+ 'To begin using the new format, pass an empty string for \'continue\' ' .
+ 'in the initial query.'
+ );
+ }
}
/**
@@ -384,18 +403,18 @@ class ApiQuery extends ApiBase {
foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
- $vals['missing'] = '';
+ $vals['missing'] = true;
$pages[$fakeId] = $vals;
}
// Report any invalid titles
foreach ( $pageSet->getInvalidTitles() as $fakeId => $title ) {
- $pages[$fakeId] = array( 'title' => $title, 'invalid' => '' );
+ $pages[$fakeId] = array( 'title' => $title, 'invalid' => true );
}
// Report any missing page ids
foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
$pages[$pageid] = array(
'pageid' => $pageid,
- 'missing' => ''
+ 'missing' => true
);
}
// Report special pages
@@ -403,15 +422,15 @@ class ApiQuery extends ApiBase {
foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
- $vals['special'] = '';
+ $vals['special'] = true;
if ( $title->isSpecialPage() &&
!SpecialPageFactory::exists( $title->getDBkey() )
) {
- $vals['missing'] = '';
+ $vals['missing'] = true;
} elseif ( $title->getNamespace() == NS_MEDIA &&
!wfFindFile( $title )
) {
- $vals['missing'] = '';
+ $vals['missing'] = true;
}
$pages[$fakeId] = $vals;
}
@@ -425,15 +444,18 @@ class ApiQuery extends ApiBase {
}
if ( count( $pages ) ) {
+ $pageSet->populateGeneratorData( $pages );
+ ApiResult::setArrayType( $pages, 'BCarray' );
+
if ( $this->mParams['indexpageids'] ) {
- $pageIDs = array_keys( $pages );
+ $pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
// json treats all map keys as strings - converting to match
$pageIDs = array_map( 'strval', $pageIDs );
- $result->setIndexedTagName( $pageIDs, 'id' );
+ ApiResult::setIndexedTagName( $pageIDs, 'id' );
$fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
}
- $result->setIndexedTagName( $pages, 'page' );
+ ApiResult::setIndexedTagName( $pages, 'page' );
$fit = $fit && $result->addValue( 'query', 'pages', $pages );
}
@@ -462,7 +484,7 @@ class ApiQuery extends ApiBase {
*/
public function setGeneratorContinue( $module, $paramName, $paramValue ) {
wfDeprecated( __METHOD__, '1.24' );
- $this->getResult()->setGeneratorContinueParam( $module, $paramName, $paramValue );
+ $this->getContinuationManager()->addGeneratorContinueParam( $module, $paramName, $paramValue );
return $this->getParameter( 'continue' ) !== null;
}
@@ -504,9 +526,8 @@ class ApiQuery extends ApiBase {
$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, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( 'query', 'export', $exportxml, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, array( 'export' ) );
}
}
@@ -540,9 +561,11 @@ class ApiQuery extends ApiBase {
/**
* Override the parent to generate help messages for all available query modules.
+ * @deprecated since 1.25
* @return string
*/
public function makeHelpMsg() {
+ wfDeprecated( __METHOD__, '1.25' );
// Use parent to make default message for the query module
$msg = parent::makeHelpMsg();
@@ -562,6 +585,7 @@ class ApiQuery extends ApiBase {
/**
* For all modules of a given group, generate help messages and join them together
+ * @deprecated since 1.25
* @param string $group Module group
* @return string
*/
@@ -594,44 +618,13 @@ class ApiQuery extends ApiBase {
return true;
}
- public function getParamDescription() {
- return $this->getPageSet()->getFinalParamDescription() + array(
- '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',
- '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.',
- '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.'
- ),
- '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,',
- '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 getExamples() {
+ protected function getExamplesMessages() {
return array(
- '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=',
+ 'action=query&prop=revisions&meta=siteinfo&' .
+ 'titles=Main%20Page&rvprop=user|comment&continue='
+ => 'apihelp-query-example-revisions',
+ 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
+ => 'apihelp-query-example-allpages',
);
}
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index 79fab727..cc0b71af 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -128,15 +128,15 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$pages[] = $titleObj;
} else {
$item = array();
- ApiResult::setContent( $item, $titleObj->getText() );
+ ApiResult::setContentValue( $item, 'category', $titleObj->getText() );
if ( isset( $prop['size'] ) ) {
$item['size'] = intval( $row->cat_pages );
$item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
$item['files'] = intval( $row->cat_files );
$item['subcats'] = intval( $row->cat_subcats );
}
- if ( isset( $prop['hidden'] ) && $row->cat_hidden ) {
- $item['hidden'] = '';
+ if ( isset( $prop['hidden'] ) ) {
+ $item['hidden'] = (bool)$row->cat_hidden;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $item );
if ( !$fit ) {
@@ -147,7 +147,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'c' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'c' );
} else {
$resultPageSet->populateFromTitles( $pages );
}
@@ -156,7 +156,9 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
public function getAllowedParams() {
return array(
'from' => null,
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'to' => null,
'prefix' => null,
'dir' => array(
@@ -189,32 +191,12 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- return array(
- 'from' => 'The category to start enumerating from',
- 'continue' => 'When more results are available, use this to continue',
- 'to' => 'The category to stop enumerating at',
- 'prefix' => 'Search for all category titles that begin with this value',
- 'dir' => 'Direction to sort in',
- 'min' => 'Minimum number of category members',
- 'max' => 'Maximum number of category members',
- 'limit' => 'How many categories to return',
- 'prop' => array(
- 'Which properties to get',
- ' size - Adds number of pages in the category',
- ' hidden - Tags categories that are hidden with __HIDDENCAT__',
- ),
- );
- }
-
- public function getDescription() {
- return 'Enumerate all categories.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=allcategories&acprop=size',
- 'api.php?action=query&generator=allcategories&gacprefix=List&prop=info',
+ 'action=query&list=allcategories&acprop=size'
+ => 'apihelp-query+allcategories-example-size',
+ 'action=query&generator=allcategories&gacprefix=List&prop=info'
+ => 'apihelp-query+allcategories-example-generator',
);
}
diff --git a/includes/api/ApiQueryAllDeletedRevisions.php b/includes/api/ApiQueryAllDeletedRevisions.php
new file mode 100644
index 00000000..4e4d2af7
--- /dev/null
+++ b/includes/api/ApiQueryAllDeletedRevisions.php
@@ -0,0 +1,427 @@
+<?php
+/**
+ * Created on Oct 3, 2014
+ *
+ * Copyright © 2014 Brad Jorsch "bjorsch@wikimedia.org"
+ *
+ * Heavily based on ApiQueryDeletedrevs,
+ * Copyright © 2007 Roan Kattouw "<Firstname>.<Lastname>@gmail.com"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Query module to enumerate all deleted revisions.
+ *
+ * @ingroup API
+ */
+class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'adr' );
+ }
+
+ /**
+ * @param ApiPageSet $resultPageSet
+ * @return void
+ */
+ protected function run( ApiPageSet $resultPageSet = null ) {
+ $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'
+ );
+ }
+
+ $db = $this->getDB();
+ $params = $this->extractRequestParams( false );
+
+ $result = $this->getResult();
+ $pageSet = $this->getPageSet();
+ $titles = $pageSet->getTitles();
+
+ // This module operates in two modes:
+ // 'user': List deleted revs by a certain user
+ // 'all': List all deleted revs in NS
+ $mode = 'all';
+ if ( !is_null( $params['user'] ) ) {
+ $mode = 'user';
+ }
+
+ if ( $mode == 'user' ) {
+ foreach ( array( 'from', 'to', 'prefix', 'excludeuser' ) as $param ) {
+ if ( !is_null( $params[$param] ) ) {
+ $p = $this->getModulePrefix();
+ $this->dieUsage( "The '{$p}{$param}' parameter cannot be used with '{$p}user'",
+ 'badparams' );
+ }
+ }
+ } else {
+ foreach ( array( 'start', 'end' ) as $param ) {
+ if ( !is_null( $params[$param] ) ) {
+ $p = $this->getModulePrefix();
+ $this->dieUsage( "The '{$p}{$param}' parameter may only be used with '{$p}user'",
+ 'badparams' );
+ }
+ }
+ }
+
+ $this->addTables( 'archive' );
+ if ( $resultPageSet === null ) {
+ $this->parseParameters( $params );
+ $this->addFields( Revision::selectArchiveFields() );
+ $this->addFields( array( 'ar_title', 'ar_namespace' ) );
+ } else {
+ $this->limit = $this->getParameter( 'limit' ) ?: 10;
+ $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id' ) );
+ }
+
+ if ( $this->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 ( $this->fetchContent ) {
+ // 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.
+ $this->addTables( 'text' );
+ $this->addJoinConds(
+ array( 'text' => array( 'LEFT JOIN', array( 'ar_text_id=old_id' ) ) )
+ );
+ $this->addFields( array( 'ar_text', 'ar_flags', 'old_text', 'old_flags' ) );
+
+ // This also means stricter restrictions
+ if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision content',
+ 'permissiondenied'
+ );
+ }
+ }
+
+ $dir = $params['dir'];
+ $miser_ns = null;
+
+ if ( $mode == 'all' ) {
+ if ( $params['namespace'] !== null ) {
+ $namespaces = $params['namespace'];
+ $this->addWhereFld( 'ar_namespace', $namespaces );
+ } else {
+ $namespaces = MWNamespace::getValidNamespaces();
+ }
+
+ // For from/to/prefix, we have to consider the potential
+ // transformations of the title in all specified namespaces.
+ // Generally there will be only one transformation, but wikis with
+ // some namespaces case-sensitive could have two.
+ if ( $params['from'] !== null || $params['to'] !== null ) {
+ $isDirNewer = ( $dir === 'newer' );
+ $after = ( $isDirNewer ? '>=' : '<=' );
+ $before = ( $isDirNewer ? '<=' : '>=' );
+ $where = array();
+ foreach ( $namespaces as $ns ) {
+ $w = array();
+ if ( $params['from'] !== null ) {
+ $w[] = 'ar_title' . $after .
+ $db->addQuotes( $this->titlePartToKey( $params['from'], $ns ) );
+ }
+ if ( $params['to'] !== null ) {
+ $w[] = 'ar_title' . $before .
+ $db->addQuotes( $this->titlePartToKey( $params['to'], $ns ) );
+ }
+ $w = $db->makeList( $w, LIST_AND );
+ $where[$w][] = $ns;
+ }
+ if ( count( $where ) == 1 ) {
+ $where = key( $where );
+ $this->addWhere( $where );
+ } else {
+ $where2 = array();
+ foreach ( $where as $w => $ns ) {
+ $where2[] = $db->makeList( array( $w, 'ar_namespace' => $ns ), LIST_AND );
+ }
+ $this->addWhere( $db->makeList( $where2, LIST_OR ) );
+ }
+ }
+
+ if ( isset( $params['prefix'] ) ) {
+ $where = array();
+ foreach ( $namespaces as $ns ) {
+ $w = 'ar_title' . $db->buildLike(
+ $this->titlePartToKey( $params['prefix'], $ns ),
+ $db->anyString() );
+ $where[$w][] = $ns;
+ }
+ if ( count( $where ) == 1 ) {
+ $where = key( $where );
+ $this->addWhere( $where );
+ } else {
+ $where2 = array();
+ foreach ( $where as $w => $ns ) {
+ $where2[] = $db->makeList( array( $w, 'ar_namespace' => $ns ), LIST_AND );
+ }
+ $this->addWhere( $db->makeList( $where2, LIST_OR ) );
+ }
+ }
+ } else {
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $miser_ns = $params['namespace'];
+ } else {
+ $this->addWhereFld( 'ar_namespace', $params['namespace'] );
+ }
+ $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
+ }
+
+ if ( !is_null( $params['user'] ) ) {
+ $this->addWhereFld( 'ar_user_text', $params['user'] );
+ } elseif ( !is_null( $params['excludeuser'] ) ) {
+ $this->addWhere( 'ar_user_text != ' .
+ $db->addQuotes( $params['excludeuser'] ) );
+ }
+
+ 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'] );
+ $op = ( $dir == 'newer' ? '>' : '<' );
+ if ( $mode == 'all' ) {
+ $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 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', $this->limit + 1 );
+
+ $sort = ( $dir == 'newer' ? '' : ' DESC' );
+ $orderby = array();
+ if ( $mode == 'all' ) {
+ // Targeting index name_title_timestamp
+ if ( $params['namespace'] === null || count( array_unique( $params['namespace'] ) ) > 1 ) {
+ $orderby[] = "ar_namespace $sort";
+ }
+ $orderby[] = "ar_title $sort";
+ $orderby[] = "ar_timestamp $sort";
+ $orderby[] = "ar_id $sort";
+ } else {
+ // Targeting index usertext_timestamp
+ // 'user' is always constant.
+ $orderby[] = "ar_timestamp $sort";
+ $orderby[] = "ar_id $sort";
+ }
+ $this->addOption( 'ORDER BY', $orderby );
+
+ $res = $this->select( __METHOD__ );
+ $pageMap = array(); // Maps ns&title to array index
+ $count = 0;
+ $nextIndex = 0;
+ $generated = array();
+ foreach ( $res as $row ) {
+ if ( ++$count > $this->limit ) {
+ // We've had enough
+ if ( $mode == 'all' ) {
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ } else {
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
+ }
+ break;
+ }
+
+ // Miser mode namespace check
+ if ( $miser_ns !== null && !in_array( $row->ar_namespace, $miser_ns ) ) {
+ continue;
+ }
+
+ if ( $resultPageSet !== null ) {
+ if ( $params['generatetitles'] ) {
+ $key = "{$row->ar_namespace}:{$row->ar_title}";
+ if ( !isset( $generated[$key] ) ) {
+ $generated[$key] = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ }
+ } else {
+ $generated[] = $row->ar_rev_id;
+ }
+ } else {
+ $revision = Revision::newFromArchiveRow( $row );
+ $rev = $this->extractRevisionInfo( $revision, $row );
+
+ if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
+ $index = $nextIndex++;
+ $pageMap[$row->ar_namespace][$row->ar_title] = $index;
+ $title = $revision->getTitle();
+ $a = array(
+ 'pageid' => $title->getArticleID(),
+ 'revisions' => array( $rev ),
+ );
+ ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
+ ApiQueryBase::addTitleInfo( $a, $title );
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), $index, $a );
+ } else {
+ $index = $pageMap[$row->ar_namespace][$row->ar_title];
+ $fit = $result->addValue(
+ array( 'query', $this->getModuleName(), $index, 'revisions' ),
+ null, $rev );
+ }
+ if ( !$fit ) {
+ if ( $mode == 'all' ) {
+ $this->setContinueEnumParameter( 'continue',
+ "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ } else {
+ $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
+ }
+ break;
+ }
+ }
+ }
+
+ if ( $resultPageSet !== null ) {
+ if ( $params['generatetitles'] ) {
+ $resultPageSet->populateFromTitles( $generated );
+ } else {
+ $resultPageSet->populateFromRevisionIDs( $generated );
+ }
+ } else {
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
+ }
+ }
+
+ public function getAllowedParams() {
+ $ret = parent::getAllowedParams() + array(
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'namespace' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_DFLT => null,
+ ),
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'useronly' ) ),
+ ),
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'useronly' ) ),
+ ),
+ 'dir' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'newer',
+ 'older'
+ ),
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
+ ),
+ 'from' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'to' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'prefix' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'excludeuser' => array(
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'nonuseronly' ) ),
+ ),
+ 'tag' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
+ 'generatetitles' => array(
+ ApiBase::PARAM_DFLT => false
+ ),
+ );
+
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['user'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+ 'apihelp-query+alldeletedrevisions-param-miser-user-namespace',
+ );
+ $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+ 'apihelp-query+alldeletedrevisions-param-miser-user-namespace',
+ );
+ }
+
+ return $ret;
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=query&list=alldeletedrevisions&adruser=Example&adrlimit=50'
+ => 'apihelp-query+alldeletedrevisions-example-user',
+ 'action=query&list=alldeletedrevisions&adrdir=newer&adrlimit=50'
+ => 'apihelp-query+alldeletedrevisions-example-ns-main',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Alldeletedrevisions';
+ }
+}
diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php
index 9dc5f69a..381938bd 100644
--- a/includes/api/ApiQueryAllImages.php
+++ b/includes/api/ApiQueryAllImages.php
@@ -232,10 +232,25 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
$this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' );
}
- list( $major, $minor ) = File::splitMime( $params['mime'] );
-
- $this->addWhereFld( 'img_major_mime', $major );
- $this->addWhereFld( 'img_minor_mime', $minor );
+ $mimeConds = array();
+ foreach ( $params['mime'] as $mime ) {
+ list( $major, $minor ) = File::splitMime( $mime );
+ $mimeConds[] = $db->makeList(
+ array(
+ 'img_major_mime' => $major,
+ 'img_minor_mime' => $minor,
+ ),
+ LIST_AND
+ );
+ }
+ // safeguard against internal_api_error_DBQueryError
+ if ( count( $mimeConds ) > 0 ) {
+ $this->addWhere( $db->makeList( $mimeConds, LIST_OR ) );
+ } else {
+ // no MIME types, no files
+ $this->getResult()->addValue( 'query', $this->getModuleName(), array() );
+ return;
+ }
}
$limit = $params['limit'];
@@ -293,14 +308,14 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'img' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'img' );
} else {
$resultPageSet->populateFromTitles( $titles );
}
}
public function getAllowedParams() {
- return array(
+ $ret = array(
'sort' => array(
ApiBase::PARAM_DFLT => 'name',
ApiBase::PARAM_TYPE => array(
@@ -321,7 +336,9 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
),
'from' => null,
'to' => null,
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'start' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
@@ -331,7 +348,9 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'prop' => array(
ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames( $this->propertyFilter ),
ApiBase::PARAM_DFLT => 'timestamp|url',
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+imageinfo-param-prop',
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => ApiQueryImageInfo::getPropertyMessages( $this->propertyFilter ),
),
'prefix' => null,
'minsize' => array(
@@ -353,7 +372,10 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
'nobots'
)
),
- 'mime' => null,
+ 'mime' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -362,57 +384,28 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
);
- }
- public function getParamDescription() {
- $p = $this->getModulePrefix();
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['mime'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
+ }
- return array(
- 'sort' => 'Property to sort by',
- 'dir' => 'The direction in which to list',
- 'from' => "The image title to start enumerating from. Can only be used with {$p}sort=name",
- 'to' => "The image title to stop enumerating at. Can only be used with {$p}sort=name",
- 'continue' => 'When more results are available, use this to continue',
- '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",
- '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",
- 'mime' => 'What MIME type to search for. e.g. image/jpeg. Disabled in Miser Mode',
- 'limit' => 'How many images in total to return',
- );
+ return $ret;
}
private $propertyFilter = array( 'archivename', 'thumbmime', 'uploadwarning' );
- public function getDescription() {
- return 'Enumerate all images sequentially.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=allimages&aifrom=B' => array(
- '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(
- '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(
- 'Using as Generator',
- 'Show info about 4 files starting at the letter "T"',
- ),
+ 'action=query&list=allimages&aifrom=B'
+ => 'apihelp-query+allimages-example-B',
+ 'action=query&list=allimages&aiprop=user|timestamp|url&' .
+ 'aisort=timestamp&aidir=older'
+ => 'apihelp-query+allimages-example-recent',
+ 'action=query&list=allimages&aimime=image/png|image/gif'
+ => 'apihelp-query+allimages-example-mimetypes',
+ 'action=query&generator=allimages&gailimit=4&' .
+ 'gaifrom=T&prop=imageinfo'
+ => 'apihelp-query+allimages-example-generator',
);
}
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index 903dee42..fadecfb4 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -31,13 +31,12 @@
*/
class ApiQueryAllLinks extends ApiQueryGeneratorBase {
- private $table, $tablePrefix, $indexTag,
- $description, $descriptionWhat, $descriptionTargets, $descriptionLinking;
+ private $table, $tablePrefix, $indexTag;
private $fieldTitle = 'title';
private $dfltNamespace = NS_MAIN;
private $hasNamespace = true;
private $useIndex = null;
- private $props = array(), $propHelp = array();
+ private $props = array();
public function __construct( ApiQuery $query, $moduleName ) {
switch ( $moduleName ) {
@@ -47,10 +46,6 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$this->tablePrefix = 'pl_';
$this->useIndex = 'pl_namespace';
$this->indexTag = 'l';
- $this->description = 'Enumerate all links that point to a given namespace';
- $this->descriptionWhat = 'link';
- $this->descriptionTargets = 'linked titles';
- $this->descriptionLinking = 'linking';
break;
case 'alltransclusions':
$prefix = 'at';
@@ -59,11 +54,6 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$this->dfltNamespace = NS_TEMPLATE;
$this->useIndex = 'tl_namespace';
$this->indexTag = 't';
- $this->description =
- 'List all transclusions (pages embedded using {{x}}), including non-existing';
- $this->descriptionWhat = 'transclusion';
- $this->descriptionTargets = 'transcluded titles';
- $this->descriptionLinking = 'transcluding';
break;
case 'allfileusages':
$prefix = 'af';
@@ -73,28 +63,16 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$this->dfltNamespace = NS_FILE;
$this->hasNamespace = false;
$this->indexTag = 'f';
- $this->description = 'List all file usages, including non-existing';
- $this->descriptionWhat = 'file';
- $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' );
@@ -222,7 +200,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $vals = array();
+ $vals = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
if ( $fld_ids ) {
$vals['fromid'] = intval( $row->pl_from );
}
@@ -252,7 +232,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $this->indexTag );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), $this->indexTag );
} elseif ( $params['unique'] ) {
$resultPageSet->populateFromTitles( $titles );
} else {
@@ -262,7 +242,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
public function getAllowedParams() {
$allowedParams = array(
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'from' => null,
'to' => null,
'prefix' => null,
@@ -300,59 +282,20 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
return $allowedParams;
}
- public function getParamDescription() {
- $p = $this->getModulePrefix();
- $what = $this->descriptionWhat;
- $targets = $this->descriptionTargets;
- $linking = $this->descriptionLinking;
- $paramDescription = array(
- 'from' => "The title of the $what to start enumerating from",
- '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=" .
- 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",
- ),
- '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 getDescription() {
- return $this->description;
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
$p = $this->getModulePrefix();
$name = $this->getModuleName();
- $what = $this->descriptionWhat;
- $targets = $this->descriptionTargets;
+ $path = $this->getModulePath();
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",
- "api.php?action=query&list={$name}&{$p}unique=&{$p}from=B"
- => "List unique $targets",
- "api.php?action=query&generator={$name}&g{$p}unique=&g{$p}from=B"
- => "Gets all $targets, marking the missing ones",
- "api.php?action=query&generator={$name}&g{$p}from=B"
- => "Gets pages containing the {$what}s",
+ "action=query&list={$name}&{$p}from=B&{$p}prop=ids|title"
+ => "apihelp-$path-example-B",
+ "action=query&list={$name}&{$p}unique=&{$p}from=B"
+ => "apihelp-$path-example-unique",
+ "action=query&generator={$name}&g{$p}unique=&g{$p}from=B"
+ => "apihelp-$path-example-unique-generator",
+ "action=query&generator={$name}&g{$p}from=B"
+ => "apihelp-$path-example-generator",
);
}
diff --git a/includes/api/ApiQueryAllMessages.php b/includes/api/ApiQueryAllMessages.php
index a75a16fc..44af83d0 100644
--- a/includes/api/ApiQueryAllMessages.php
+++ b/includes/api/ApiQueryAllMessages.php
@@ -146,7 +146,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
$messageIsCustomised = isset( $customisedMessages['pages'][$langObj->ucfirst( $message )] );
if ( $customised === $messageIsCustomised ) {
if ( $customised ) {
- $a['customised'] = '';
+ $a['customised'] = true;
}
} else {
continue;
@@ -156,7 +156,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
$msg = wfMessage( $message, $args )->inLanguage( $langObj );
if ( !$msg->exists() ) {
- $a['missing'] = '';
+ $a['missing'] = true;
} else {
// Check if the parser is enabled:
if ( $params['enableparser'] ) {
@@ -165,12 +165,12 @@ class ApiQueryAllMessages extends ApiQueryBase {
$msgString = $msg->plain();
}
if ( !$params['nocontent'] ) {
- ApiResult::setContent( $a, $msgString );
+ ApiResult::setContentValue( $a, 'content', $msgString );
}
if ( isset( $prop['default'] ) ) {
$default = wfMessage( $message )->inLanguage( $langObj )->useDatabase( false );
if ( !$default->exists() ) {
- $a['defaultmissing'] = '';
+ $a['defaultmissing'] = true;
} elseif ( $default->plain() != $msgString ) {
$a['default'] = $default->plain();
}
@@ -183,7 +183,7 @@ class ApiQueryAllMessages extends ApiQueryBase {
}
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'message' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'message' );
}
public function getCacheMode( $params ) {
@@ -235,35 +235,12 @@ class ApiQueryAllMessages extends ApiQueryBase {
);
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
return array(
- '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.' ),
- '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",
- ),
- 'title' => 'Page name to use as context when parsing message (for enableparser option)',
- 'args' => 'Arguments to be substituted into message',
- 'prefix' => 'Return messages with this prefix',
- 'filter' => 'Return only messages with names that contain this string',
- 'customised' => 'Return only messages in this customisation state',
- 'lang' => 'Return messages in this language',
- 'from' => 'Return messages starting at this message',
- 'to' => 'Return messages ending at this message',
- );
- }
-
- public function getDescription() {
- return 'Return messages from this site.';
- }
-
- public function getExamples() {
- return array(
- 'api.php?action=query&meta=allmessages&amprefix=ipb-',
- 'api.php?action=query&meta=allmessages&ammessages=august|mainpage&amlang=de',
+ 'action=query&meta=allmessages&amprefix=ipb-'
+ => 'apihelp-query+allmessages-example-ipb',
+ 'action=query&meta=allmessages&ammessages=august|mainpage&amlang=de'
+ => 'apihelp-query+allmessages-example-de',
);
}
diff --git a/includes/api/ApiQueryAllPages.php b/includes/api/ApiQueryAllPages.php
index b7bd65a5..0149ad2f 100644
--- a/includes/api/ApiQueryAllPages.php
+++ b/includes/api/ApiQueryAllPages.php
@@ -168,9 +168,23 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
$this->addTables( 'langlinks' );
$this->addWhere( 'page_id=ll_from' );
$this->addOption( 'STRAIGHT_JOIN' );
- // We have to GROUP BY all selected fields to stop
- // PostgreSQL from whining
- $this->addOption( 'GROUP BY', $selectFields );
+
+ // MySQL filesorts if we use a GROUP BY that works with the rules
+ // in the 1992 SQL standard (it doesn't like having the
+ // constant-in-WHERE page_namespace column in there). Using the
+ // 1999 rules works fine, but that breaks other DBs. Sigh.
+ /// @todo Once we drop support for 1992-rule DBs, we can simplify this.
+ $dbType = $db->getType();
+ if ( $dbType === 'mysql' || $dbType === 'sqlite' ||
+ $dbType === 'postgres' && $db->getServerVersion() >= 9.1
+ ) {
+ // 1999 rules, or screw-the-rules
+ $this->addOption( 'GROUP BY', array( 'page_title', 'page_id' ) );
+ } else {
+ // 1992 rules
+ $this->addOption( 'GROUP BY', $selectFields );
+ }
+
$forceNameTitleIndex = false;
}
@@ -220,14 +234,16 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'p' );
}
}
public function getAllowedParams() {
return array(
'from' => null,
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'to' => null,
'prefix' => null,
'namespace' => array(
@@ -297,54 +313,15 @@ 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',
- 'to' => 'The page title to stop enumerating at',
- 'prefix' => 'Search for all page titles that begin with this value',
- 'namespace' => 'The namespace to enumerate',
- 'filterredir' => 'Which pages to list',
- 'dir' => 'The direction in which to list',
- 'minsize' => 'Limit to pages with at least this many bytes',
- '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)",
- 'filterlanglinks' => array(
- 'Filter based on whether a page has langlinks',
- 'Note that this may not consider langlinks added by extensions.',
- ),
- 'limit' => 'How many total pages to return.',
- 'prexpiry' => array(
- 'Which protection expiry to filter the page on',
- ' indefinite - Get only pages with indefinite protection expiry',
- ' definite - Get only pages with a definite (specific) protection expiry',
- ' all - Get pages with any protections expiry'
- ),
- );
- }
-
- public function getDescription() {
- return 'Enumerate all pages sequentially in a given namespace.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=allpages&apfrom=B' => array(
- 'Simple Use',
- 'Show a list of pages starting at the letter "B"',
- ),
- 'api.php?action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info' => array(
- 'Using as Generator',
- 'Show info about 4 pages starting at the letter "T"',
- ),
- 'api.php?action=query&generator=allpages&gaplimit=2&' .
+ 'action=query&list=allpages&apfrom=B'
+ => 'apihelp-query+allpages-example-B',
+ 'action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info'
+ => 'apihelp-query+allpages-example-generator',
+ '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"' )
+ => 'apihelp-query+allpages-example-generator-revisions',
);
}
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index affddda7..0cea84f8 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -48,11 +48,6 @@ class ApiQueryAllUsers extends ApiQueryBase {
$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'];
@@ -72,7 +67,6 @@ class ApiQueryAllUsers extends ApiQueryBase {
$limit = $params['limit'];
$this->addTables( 'user' );
- $useIndex = true;
$dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
$from = is_null( $params['from'] ) ? null : $this->getCanonicalUserName( $params['from'] );
@@ -82,6 +76,10 @@ class ApiQueryAllUsers extends ApiQueryBase {
# despite the JOIN condition, so manually sort on the correct one.
$userFieldToSort = $params['activeusers'] ? 'qcc_title' : 'user_name';
+ # Some of these subtable joins are going to give us duplicate rows, so
+ # calculate the maximum number of duplicates we might see.
+ $maxDuplicateRows = 1;
+
$this->addWhereRange( $userFieldToSort, $dir, $from, $to );
if ( !is_null( $params['prefix'] ) ) {
@@ -97,7 +95,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() ), '' );
+ $this->getResult()->addIndexedTagName( array( 'query', $this->getModuleName() ), '' );
return;
}
@@ -116,16 +114,17 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
if ( !is_null( $params['group'] ) && count( $params['group'] ) ) {
- $useIndex = false;
- // Filter only users that belong to a given group
+ // Filter only users that belong to a given group. This might
+ // produce as many rows-per-user as there are groups being checked.
$this->addTables( 'user_groups', 'ug1' );
$this->addJoinConds( array( 'ug1' => array( 'INNER JOIN', array( 'ug1.ug_user=user_id',
'ug1.ug_group' => $params['group'] ) ) ) );
+ $maxDuplicateRows *= count( $params['group'] );
}
if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) {
- $useIndex = false;
- // Filter only users don't belong to a given group
+ // Filter only users don't belong to a given group. This can only
+ // produce one row-per-user, because we only keep on "no match".
$this->addTables( 'user_groups', 'ug1' );
if ( count( $params['excludegroup'] ) == 1 ) {
@@ -149,22 +148,16 @@ class ApiQueryAllUsers extends ApiQueryBase {
$this->showHiddenUsersAddBlockInfo( $fld_blockinfo );
if ( $fld_groups || $fld_rights ) {
- // Show the groups the given users belong to
- // request more than needed to avoid not getting all rows that belong to one user
- $groupCount = count( User::getAllGroups() );
- $sqlLimit = $limit + $groupCount + 1;
-
- $this->addTables( 'user_groups', 'ug2' );
- $this->addJoinConds( array( 'ug2' => array( 'LEFT JOIN', 'ug2.ug_user=user_id' ) ) );
- $this->addFields( array( 'ug_group2' => 'ug2.ug_group' ) );
- } else {
- $sqlLimit = $limit + 1;
+ $this->addFields( array( 'groups' =>
+ $db->buildGroupConcatField( '|', 'user_groups', 'ug_group', 'ug_user=user_id' )
+ ) );
}
if ( $params['activeusers'] ) {
$activeUserSeconds = $activeUserDays * 86400;
- // Filter query to only include users in the active users cache
+ // Filter query to only include users in the active users cache.
+ // There shouldn't be any duplicate rows in querycachetwo here.
$this->addTables( 'querycachetwo' );
$this->addJoinConds( array( 'querycachetwo' => array(
'INNER JOIN', array(
@@ -190,6 +183,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
) );
}
+ $sqlLimit = $limit + $maxDuplicateRows;
$this->addOption( 'LIMIT', $sqlLimit );
$this->addFields( array(
@@ -199,148 +193,112 @@ class ApiQueryAllUsers extends ApiQueryBase {
$this->addFieldsIf( 'user_editcount', $fld_editcount );
$this->addFieldsIf( 'user_registration', $fld_registration );
- if ( $useIndex ) {
- $this->addOption( 'USE INDEX', array( 'user' => 'user_name' ) );
- }
-
$res = $this->select( __METHOD__ );
-
$count = 0;
- $lastUserData = false;
+ $countDuplicates = 0;
$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.
-
foreach ( $res as $row ) {
$count++;
- if ( $lastUser !== $row->user_name ) {
- // Save the last pass's user data
- if ( is_array( $lastUserData ) ) {
- 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;
-
- if ( !$fit ) {
- $this->setContinueEnumParameter( 'from', $lastUserData['name'] );
- break;
- }
+ if ( $lastUser === $row->user_name ) {
+ // Duplicate row due to one of the needed subtable joins.
+ // Ignore it, but count the number of them to sanely handle
+ // miscalculation of $maxDuplicateRows.
+ $countDuplicates++;
+ if ( $countDuplicates == $maxDuplicateRows ) {
+ ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' );
}
+ continue;
+ }
- if ( $count > $limit ) {
- // 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;
- }
+ $countDuplicates = 0;
+ $lastUser = $row->user_name;
- // Record new user's data
- $lastUser = $row->user_name;
- $lastUserData = array(
- 'userid' => $row->user_id,
- 'name' => $lastUser,
- );
- if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) {
- $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;
- }
- if ( $row->ipb_deleted ) {
- $lastUserData['hidden'] = '';
- }
- if ( $fld_editcount ) {
- $lastUserData['editcount'] = intval( $row->user_editcount );
- }
- if ( $params['activeusers'] ) {
- $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 ?
- wfTimestamp( TS_ISO_8601, $row->user_registration ) : '';
- }
+ if ( $count > $limit ) {
+ // 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;
}
- if ( $sqlLimit == $count ) {
- // @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'
- );
+ if ( $count == $sqlLimit ) {
+ // Should never hit this (either the $countDuplicates check or
+ // the $count > $limit check should hit first), but check it
+ // anyway just in case.
+ ApiBase::dieDebug( __METHOD__, 'Saw more duplicate rows than expected' );
}
- $lastUserObj = User::newFromId( $row->user_id );
-
- // Add user's group info
- if ( $fld_groups ) {
- if ( !isset( $lastUserData['groups'] ) ) {
- if ( $lastUserObj ) {
- $lastUserData['groups'] = $lastUserObj->getAutomaticGroups();
- } else {
- // This should not normally happen
- $lastUserData['groups'] = array();
- }
- }
-
- if ( !is_null( $row->ug_group2 ) ) {
- $lastUserData['groups'][] = $row->ug_group2;
- }
-
- $result->setIndexedTagName( $lastUserData['groups'], 'g' );
+ if ( $params['activeusers'] && $row->recentactions === 0 ) {
+ // activeusers cache was out of date
+ continue;
}
- if ( $fld_implicitgroups && !isset( $lastUserData['implicitgroups'] ) && $lastUserObj ) {
- $lastUserData['implicitgroups'] = $lastUserObj->getAutomaticGroups();
- $result->setIndexedTagName( $lastUserData['implicitgroups'], 'g' );
+ $data = array(
+ 'userid' => $row->user_id,
+ 'name' => $row->user_name,
+ );
+
+ if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) {
+ $data['blockid'] = $row->ipb_id;
+ $data['blockedby'] = $row->ipb_by_text;
+ $data['blockedbyid'] = $row->ipb_by;
+ $data['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
+ $data['blockreason'] = $row->ipb_reason;
+ $data['blockexpiry'] = $row->ipb_expiry;
+ }
+ if ( $row->ipb_deleted ) {
+ $data['hidden'] = true;
+ }
+ if ( $fld_editcount ) {
+ $data['editcount'] = intval( $row->user_editcount );
+ }
+ if ( $params['activeusers'] ) {
+ $data['recentactions'] = intval( $row->recentactions );
+ // @todo 'recenteditcount' is set for BC, remove in 1.25
+ $data['recenteditcount'] = $data['recentactions'];
}
- if ( $fld_rights ) {
- if ( !isset( $lastUserData['rights'] ) ) {
- if ( $lastUserObj ) {
- $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
- } else {
- // This should not normally happen
- $lastUserData['rights'] = array();
- }
+ if ( $fld_registration ) {
+ $data['registration'] = $row->user_registration ?
+ wfTimestamp( TS_ISO_8601, $row->user_registration ) : '';
+ }
+
+ if ( $fld_implicitgroups || $fld_groups || $fld_rights ) {
+ $user = User::newFromId( $row->user_id );
+ $implicitGroups = User::newFromId( $row->user_id )->getAutomaticGroups();
+ if ( isset( $row->groups ) && $row->groups !== '' ) {
+ $groups = array_merge( $implicitGroups, explode( '|', $row->groups ) );
+ } else {
+ $groups = $implicitGroups;
}
- if ( !is_null( $row->ug_group2 ) ) {
- $lastUserData['rights'] = array_unique( array_merge( $lastUserData['rights'],
- User::getGroupPermissions( array( $row->ug_group2 ) ) ) );
+ if ( $fld_groups ) {
+ $data['groups'] = $groups;
+ ApiResult::setIndexedTagName( $data['groups'], 'g' );
+ ApiResult::setArrayType( $data['groups'], 'array' );
}
- $result->setIndexedTagName( $lastUserData['rights'], 'r' );
+ if ( $fld_implicitgroups ) {
+ $data['implicitgroups'] = $implicitGroups;
+ ApiResult::setIndexedTagName( $data['implicitgroups'], 'g' );
+ ApiResult::setArrayType( $data['implicitgroups'], 'array' );
+ }
+
+ if ( $fld_rights ) {
+ $data['rights'] = User::getGroupPermissions( $groups );
+ ApiResult::setIndexedTagName( $data['rights'], 'r' );
+ ApiResult::setArrayType( $data['rights'], 'array' );
+ }
}
- }
- if ( is_array( $lastUserData ) &&
- !( $params['activeusers'] && $lastUserData['recentactions'] === 0 )
- ) {
- $fit = $result->addValue( array( 'query', $this->getModuleName() ),
- null, $lastUserData );
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $data );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'from', $lastUserData['name'] );
+ $this->setContinueEnumParameter( 'from', $data['name'] );
+ break;
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'u' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'u' );
}
public function getCacheMode( $params ) {
@@ -392,43 +350,20 @@ class ApiQueryAllUsers extends ApiQueryBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'witheditsonly' => false,
- 'activeusers' => false,
- );
- }
-
- public function getParamDescription() {
- return array(
- 'from' => 'The user name to start enumerating from',
- 'to' => 'The user name to stop enumerating at',
- 'prefix' => 'Search for all users that begin with this value',
- '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)',
- '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',
- ' 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)',
+ 'activeusers' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => array(
+ 'apihelp-query+allusers-param-activeusers',
+ $this->getConfig()->get( 'ActiveUserDays' )
+ ),
),
- 'limit' => 'How many total user names to return',
- 'witheditsonly' => 'Only list users who have made edits',
- 'activeusers' => "Only list users active in the last {$this->getConfig()->get( 'ActiveUserDays' )} days(s)"
);
}
- public function getDescription() {
- return 'Enumerate all registered users.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=allusers&aufrom=Y',
+ 'action=query&list=allusers&aufrom=Y'
+ => 'apihelp-query+allusers-example-Y',
);
}
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index c141246d..1df14e0b 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -39,8 +39,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
*/
private $rootTitle;
- private $params, $contID, $redirID, $redirect;
- private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_fields, $hasNS;
+ private $params, $cont, $redirect;
+ private $bl_ns, $bl_from, $bl_from_ns, $bl_table, $bl_code, $bl_title, $bl_fields, $hasNS;
/**
* Maps ns and title to pageid
@@ -84,6 +84,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
parent::__construct( $query, $moduleName, $code );
$this->bl_ns = $prefix . '_namespace';
$this->bl_from = $prefix . '_from';
+ $this->bl_from_ns = $prefix . '_from_namespace';
$this->bl_table = $settings['linktbl'];
$this->bl_code = $code;
$this->helpUrl = $settings['helpurl'];
@@ -119,12 +120,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
* @param ApiPageSet $resultPageSet
* @return void
*/
- private function prepareFirstQuery( $resultPageSet = null ) {
- /* SELECT page_id, page_title, page_namespace, page_is_redirect
- * FROM pagelinks, page WHERE pl_from=page_id
- * AND pl_title='Foo' AND pl_namespace=0
- * LIMIT 11 ORDER BY pl_from
- */
+ private function runFirstQuery( $resultPageSet = null ) {
$this->addTables( array( $this->bl_table, 'page' ) );
$this->addWhere( "{$this->bl_from}=page_id" );
if ( is_null( $resultPageSet ) ) {
@@ -132,18 +128,25 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
} else {
$this->addFields( $resultPageSet->getPageTableFields() );
}
+ $this->addFields( array( 'page_is_redirect', 'from_ns' => 'page_namespace' ) );
- $this->addFields( 'page_is_redirect' );
$this->addWhereFld( $this->bl_title, $this->rootTitle->getDBkey() );
-
if ( $this->hasNS ) {
$this->addWhereFld( $this->bl_ns, $this->rootTitle->getNamespace() );
}
- $this->addWhereFld( 'page_namespace', $this->params['namespace'] );
+ $this->addWhereFld( $this->bl_from_ns, $this->params['namespace'] );
- if ( !is_null( $this->contID ) ) {
+ if ( count( $this->cont ) >= 2 ) {
$op = $this->params['dir'] == 'descending' ? '<' : '>';
- $this->addWhere( "{$this->bl_from}$op={$this->contID}" );
+ if ( count( $this->params['namespace'] ) > 1 ) {
+ $this->addWhere(
+ "{$this->bl_from_ns} $op {$this->cont[0]} OR " .
+ "({$this->bl_from_ns} = {$this->cont[0]} AND " .
+ "{$this->bl_from} $op= {$this->cont[1]})"
+ );
+ } else {
+ $this->addWhere( "{$this->bl_from} $op= {$this->cont[1]}" );
+ }
}
if ( $this->params['filterredir'] == 'redirects' ) {
@@ -156,20 +159,56 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'LIMIT', $this->params['limit'] + 1 );
$sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' );
- $this->addOption( 'ORDER BY', $this->bl_from . $sort );
+ $orderBy = array();
+ if ( count( $this->params['namespace'] ) > 1 ) {
+ $orderBy[] = $this->bl_from_ns . $sort;
+ }
+ $orderBy[] = $this->bl_from . $sort;
+ $this->addOption( 'ORDER BY', $orderBy );
$this->addOption( 'STRAIGHT_JOIN' );
+
+ $res = $this->select( __METHOD__ );
+ $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...
+ // Continue string may be overridden at a later step
+ $this->continueStr = "{$row->from_ns}|{$row->page_id}";
+ break;
+ }
+
+ // Fill in continuation fields for later steps
+ if ( count( $this->cont ) < 2 ) {
+ $this->cont[] = $row->from_ns;
+ $this->cont[] = $row->page_id;
+ }
+
+ $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
+ $t = Title::makeTitle( $row->page_namespace, $row->page_title );
+ if ( $row->page_is_redirect ) {
+ $this->redirTitles[] = $t;
+ }
+
+ if ( is_null( $resultPageSet ) ) {
+ $a = array( 'pageid' => intval( $row->page_id ) );
+ ApiQueryBase::addTitleInfo( $a, $t );
+ if ( $row->page_is_redirect ) {
+ $a['redirect'] = true;
+ }
+ // Put all the results in an array first
+ $this->resultArr[$a['pageid']] = $a;
+ } else {
+ $resultPageSet->processDbRow( $row );
+ }
+ }
}
/**
* @param ApiPageSet $resultPageSet
* @return void
*/
- private function prepareSecondQuery( $resultPageSet = null ) {
- /* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace
- FROM pagelinks, page WHERE pl_from=page_id
- AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1)
- ORDER BY pl_namespace, pl_title, pl_from LIMIT 11
- */
+ private function runSecondQuery( $resultPageSet = null ) {
$db = $this->getDB();
$this->addTables( array( 'page', $this->bl_table ) );
$this->addWhere( "{$this->bl_from}=page_id" );
@@ -180,7 +219,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addFields( $resultPageSet->getPageTableFields() );
}
- $this->addFields( $this->bl_title );
+ $this->addFields( array( $this->bl_title, 'from_ns' => 'page_namespace' ) );
if ( $this->hasNS ) {
$this->addFields( $this->bl_ns );
}
@@ -195,30 +234,33 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$redirDBkey = $t->getDBkey();
$titleWhere[] = "{$this->bl_title} = " . $db->addQuotes( $redirDBkey ) .
( $this->hasNS ? " AND {$this->bl_ns} = {$redirNs}" : '' );
- $allRedirNs[] = $redirNs;
- $allRedirDBkey[] = $redirDBkey;
+ $allRedirNs[$redirNs] = true;
+ $allRedirDBkey[$redirDBkey] = true;
}
$this->addWhere( $db->makeList( $titleWhere, LIST_OR ) );
$this->addWhereFld( 'page_namespace', $this->params['namespace'] );
- if ( !is_null( $this->redirID ) ) {
+ if ( count( $this->cont ) >= 6 ) {
$op = $this->params['dir'] == 'descending' ? '<' : '>';
- /** @var $first Title */
- $first = $this->redirTitles[0];
- $title = $db->addQuotes( $first->getDBkey() );
- $ns = $first->getNamespace();
- $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)))" );
- } else {
- $this->addWhere( "{$this->bl_title} $op $title OR " .
- "({$this->bl_title} = $title AND " .
- "{$this->bl_from} $op= $from)" );
+
+ $where = "{$this->bl_from} $op= {$this->cont[5]}";
+ // Don't bother with namespace, title, or from_namespace if it's
+ // otherwise constant in the where clause.
+ if ( count( $this->params['namespace'] ) > 1 ) {
+ $where = "{$this->bl_from_ns} $op {$this->cont[4]} OR " .
+ "({$this->bl_from_ns} = {$this->cont[4]} AND ($where))";
+ }
+ if ( count( $allRedirDBkey ) > 1 ) {
+ $title = $db->addQuotes( $this->cont[3] );
+ $where = "{$this->bl_title} $op $title OR " .
+ "({$this->bl_title} = $title AND ($where))";
+ }
+ if ( $this->hasNS && count( $allRedirNs ) > 1 ) {
+ $where = "{$this->bl_ns} $op {$this->cont[2]} OR " .
+ "({$this->bl_ns} = {$this->cont[2]} AND ($where))";
}
+
+ $this->addWhere( $where );
}
if ( $this->params['filterredir'] == 'redirects' ) {
$this->addWhereFld( 'page_is_redirect', 1 );
@@ -229,16 +271,57 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'LIMIT', $this->params['limit'] + 1 );
$orderBy = array();
$sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' );
- // Don't order by namespace/title if it's constant in the WHERE clause
- if ( $this->hasNS && count( array_unique( $allRedirNs ) ) != 1 ) {
+ // Don't order by namespace/title/from_namespace if it's constant in the WHERE clause
+ if ( $this->hasNS && count( $allRedirNs ) > 1 ) {
$orderBy[] = $this->bl_ns . $sort;
}
- if ( count( array_unique( $allRedirDBkey ) ) != 1 ) {
+ if ( count( $allRedirDBkey ) > 1 ) {
$orderBy[] = $this->bl_title . $sort;
}
+ if ( count( $this->params['namespace'] ) > 1 ) {
+ $orderBy[] = $this->bl_from_ns . $sort;
+ }
$orderBy[] = $this->bl_from . $sort;
$this->addOption( 'ORDER BY', $orderBy );
$this->addOption( 'USE INDEX', array( 'page' => 'PRIMARY' ) );
+
+ $res = $this->select( __METHOD__ );
+ $count = 0;
+ foreach ( $res as $row ) {
+ $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE;
+
+ if ( ++$count > $this->params['limit'] ) {
+ // We've reached the one extra which shows that there are
+ // additional pages to be had. Stop here...
+ // Note we must keep the parameters for the first query constant
+ // This may be overridden at a later step
+ $title = $row->{$this->bl_title};
+ $this->continueStr = join( '|', array_slice( $this->cont, 0, 2 ) ) .
+ "|$ns|$title|{$row->from_ns}|{$row->page_id}";
+ break;
+ }
+
+ // Fill in continuation fields for later steps
+ if ( count( $this->cont ) < 6 ) {
+ $this->cont[] = $ns;
+ $this->cont[] = $row->{$this->bl_title};
+ $this->cont[] = $row->from_ns;
+ $this->cont[] = $row->page_id;
+ }
+
+ if ( is_null( $resultPageSet ) ) {
+ $a['pageid'] = intval( $row->page_id );
+ ApiQueryBase::addTitleInfo( $a, Title::makeTitle( $row->page_namespace, $row->page_title ) );
+ if ( $row->page_is_redirect ) {
+ $a['redirect'] = true;
+ }
+ $parentID = $this->pageMap[$ns][$row->{$this->bl_title}];
+ // Put all the results in an array first
+ $this->resultArr[$parentID]['redirlinks'][$row->page_id] = $a;
+ } else {
+ $resultPageSet->processDbRow( $row );
+ }
+ }
}
/**
@@ -255,106 +338,163 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
if ( $this->params['limit'] == 'max' ) {
$this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] );
+ $result->addParsedLimit( $this->getModuleName(), $this->params['limit'] );
} else {
$this->params['limit'] = intval( $this->params['limit'] );
$this->validateLimit( 'limit', $this->params['limit'], 1, $userMax, $botMax );
}
- $this->processContinue();
- $this->prepareFirstQuery( $resultPageSet );
+ $this->rootTitle = $this->getTitleOrPageId( $this->params )->getTitle();
+
+ // 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 a file",
+ 'bad_image_title'
+ );
+ }
- $res = $this->select( __METHOD__ . '::firstQuery' );
+ // Parse and validate continuation parameter
+ $this->cont = array();
+ if ( $this->params['continue'] !== null ) {
+ $db = $this->getDB();
+ $cont = explode( '|', $this->params['continue'] );
- $count = 0;
+ switch ( count( $cont ) ) {
+ case 8:
+ // redirect page ID for result adding
+ $this->cont[7] = (int)$cont[7];
+ $this->dieContinueUsageIf( $cont[7] !== (string)$this->cont[7] );
- 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...
- // Continue string preserved in case the redirect query doesn't pass the limit
- $this->continueStr = $this->getContinueStr( $row->page_id );
- break;
- }
+ /* Fall through */
- if ( is_null( $resultPageSet ) ) {
- $this->extractRowInfo( $row );
- } else {
- $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
- if ( $row->page_is_redirect ) {
- $this->redirTitles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
- }
+ case 7:
+ // top-level page ID for result adding
+ $this->cont[6] = (int)$cont[6];
+ $this->dieContinueUsageIf( $cont[6] !== (string)$this->cont[6] );
- $resultPageSet->processDbRow( $row );
+ /* Fall through */
+
+ case 6:
+ // ns for 2nd query (even for imageusage)
+ $this->cont[2] = (int)$cont[2];
+ $this->dieContinueUsageIf( $cont[2] !== (string)$this->cont[2] );
+
+ // title for 2nd query
+ $this->cont[3] = $cont[3];
+
+ // from_ns for 2nd query
+ $this->cont[4] = (int)$cont[4];
+ $this->dieContinueUsageIf( $cont[4] !== (string)$this->cont[4] );
+
+ // from_id for 1st query
+ $this->cont[5] = (int)$cont[5];
+ $this->dieContinueUsageIf( $cont[5] !== (string)$this->cont[5] );
+
+ /* Fall through */
+
+ case 2:
+ // from_ns for 1st query
+ $this->cont[0] = (int)$cont[0];
+ $this->dieContinueUsageIf( $cont[0] !== (string)$this->cont[0] );
+
+ // from_id for 1st query
+ $this->cont[1] = (int)$cont[1];
+ $this->dieContinueUsageIf( $cont[1] !== (string)$this->cont[1] );
+
+ break;
+
+ default:
+ $this->dieContinueUsageIf( true );
}
+
+ ksort( $this->cont );
}
+ $this->runFirstQuery( $resultPageSet );
if ( $this->redirect && count( $this->redirTitles ) ) {
$this->resetQueryParams();
- $this->prepareSecondQuery( $resultPageSet );
- $res = $this->select( __METHOD__ . '::secondQuery' );
- $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 need to keep the parent page of this redir in
- if ( $this->hasNS ) {
- $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}];
- } else {
- $parentID = $this->pageMap[NS_FILE][$row->{$this->bl_title}];
- }
- $this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id );
- break;
- }
-
- if ( is_null( $resultPageSet ) ) {
- $this->extractRedirRowInfo( $row );
- } else {
- $resultPageSet->processDbRow( $row );
- }
- }
+ $this->runSecondQuery( $resultPageSet );
}
+
+ // Fill in any missing fields in case it's needed below
+ $this->cont += array( 0, 0, 0, '', 0, 0, 0 );
+
if ( is_null( $resultPageSet ) ) {
// Try to add the result data in one go and pray that it fits
- $fit = $result->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) );
+ $code = $this->bl_code;
+ $data = array_map( function ( $arr ) use ( $result, $code ) {
+ if ( isset( $arr['redirlinks'] ) ) {
+ $arr['redirlinks'] = array_values( $arr['redirlinks'] );
+ ApiResult::setIndexedTagName( $arr['redirlinks'], $code );
+ }
+ return $arr;
+ }, array_values( $this->resultArr ) );
+ $fit = $result->addValue( 'query', $this->getModuleName(), $data );
if ( !$fit ) {
// It didn't fit. Add elements one by one until the
// result is full.
+ ksort( $this->resultArr );
+ if ( count( $this->cont ) >= 7 ) {
+ $startAt = $this->cont[6];
+ } else {
+ reset( $this->resultArr );
+ $startAt = key( $this->resultArr );
+ }
+ $idx = 0;
foreach ( $this->resultArr as $pageID => $arr ) {
+ if ( $pageID < $startAt ) {
+ continue;
+ }
+
// Add the basic entry without redirlinks first
$fit = $result->addValue(
array( 'query', $this->getModuleName() ),
- null, array_diff_key( $arr, array( 'redirlinks' => '' ) ) );
+ $idx, array_diff_key( $arr, array( 'redirlinks' => '' ) ) );
if ( !$fit ) {
- $this->continueStr = $this->getContinueStr( $pageID );
+ $this->continueStr = join( '|', array_slice( $this->cont, 0, 6 ) ) .
+ "|$pageID";
break;
}
$hasRedirs = false;
- $redirLinks = isset( $arr['redirlinks'] ) ? $arr['redirlinks'] : array();
- foreach ( (array)$redirLinks as $key => $redir ) {
+ $redirLinks = isset( $arr['redirlinks'] ) ? (array)$arr['redirlinks'] : array();
+ ksort( $redirLinks );
+ if ( count( $this->cont ) >= 8 && $pageID == $startAt ) {
+ $redirStartAt = $this->cont[7];
+ } else {
+ reset( $redirLinks );
+ $redirStartAt = key( $redirLinks );
+ }
+ foreach ( $redirLinks as $key => $redir ) {
+ if ( $key < $redirStartAt ) {
+ continue;
+ }
+
$fit = $result->addValue(
- array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ),
- $key, $redir );
+ array( 'query', $this->getModuleName(), $idx, 'redirlinks' ),
+ null, $redir );
if ( !$fit ) {
- $this->continueStr = $this->getContinueRedirStr( $pageID, $redir['pageid'] );
+ $this->continueStr = join( '|', array_slice( $this->cont, 0, 6 ) ) .
+ "|$pageID|$key";
break;
}
$hasRedirs = true;
}
if ( $hasRedirs ) {
- $result->setIndexedTagName_internal(
- array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ),
+ $result->addIndexedTagName(
+ array( 'query', $this->getModuleName(), $idx, 'redirlinks' ),
$this->bl_code );
}
if ( !$fit ) {
break;
}
+
+ $idx++;
}
}
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ),
$this->bl_code
);
@@ -364,91 +504,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
}
- private function extractRowInfo( $row ) {
- $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
- $t = Title::makeTitle( $row->page_namespace, $row->page_title );
- $a = array( 'pageid' => intval( $row->page_id ) );
- ApiQueryBase::addTitleInfo( $a, $t );
- if ( $row->page_is_redirect ) {
- $a['redirect'] = '';
- $this->redirTitles[] = $t;
- }
- // Put all the results in an array first
- $this->resultArr[$a['pageid']] = $a;
- }
-
- private function extractRedirRowInfo( $row ) {
- $a['pageid'] = intval( $row->page_id );
- ApiQueryBase::addTitleInfo( $a, Title::makeTitle( $row->page_namespace, $row->page_title ) );
- if ( $row->page_is_redirect ) {
- $a['redirect'] = '';
- }
- $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE;
- $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
- );
- }
-
- protected function processContinue() {
- if ( !is_null( $this->params['continue'] ) ) {
- $this->parseContinueParam();
- } else {
- $this->rootTitle = $this->getTitleOrPageId( $this->params )->getTitle();
- }
-
- // 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'
- );
- }
- }
-
- protected function parseContinueParam() {
- $continueList = explode( '|', $this->params['continue'] );
- // expected format:
- // ns | key | id1 [| id2]
- // ns+key: root title
- // id1: first-level page ID to continue from
- // id2: second-level page ID to continue from
-
- // null stuff out now so we know what's set and what isn't
- $this->rootTitle = $this->contID = $this->redirID = null;
- $rootNs = intval( $continueList[0] );
- $this->dieContinueUsageIf( $rootNs === 0 && $continueList[0] !== '0' );
-
- $this->rootTitle = Title::makeTitleSafe( $rootNs, $continueList[1] );
- $this->dieContinueUsageIf( !$this->rootTitle );
-
- $contID = intval( $continueList[2] );
- $this->dieContinueUsageIf( $contID === 0 && $continueList[2] !== '0' );
-
- $this->contID = $contID;
- $id2 = isset( $continueList[3] ) ? $continueList[3] : null;
- $redirID = intval( $id2 );
-
- if ( $redirID === 0 && $id2 !== '0' ) {
- // This one isn't required
- return;
- }
- $this->redirID = $redirID;
- }
-
- protected function getContinueStr( $lastPageID ) {
- return $this->rootTitle->getNamespace() .
- '|' . $this->rootTitle->getDBkey() .
- '|' . $lastPageID;
- }
-
- protected function getContinueRedirStr( $lastPageID, $lastRedirID ) {
- return $this->getContinueStr( $lastPageID ) . '|' . $lastRedirID;
- }
-
public function getAllowedParams() {
$retval = array(
'title' => array(
@@ -457,7 +512,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer',
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'namespace' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => 'namespace'
@@ -493,59 +550,25 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
return $retval;
}
- public function getParamDescription() {
- $retval = array(
- 'title' => "Title to search. Cannot be used together with {$this->bl_code}pageid",
- 'pageid' => "Pageid to search. Cannot be used together with {$this->bl_code}title",
- 'continue' => 'When more results are available, use this to continue',
- 'namespace' => 'The namespace to enumerate',
- 'dir' => 'The direction in which to list',
- );
- 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).'
- ) );
- }
-
- return array_merge( $retval, array(
- 'filterredir' => 'How to filter for redirects',
- 'limit' => 'How many total pages to return'
- ) );
- }
-
- public function getDescription() {
- switch ( $this->getModuleName() ) {
- case 'backlinks':
- return 'Find all pages that link to the given page.';
- case 'embeddedin':
- 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.' );
- }
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
static $examples = array(
'backlinks' => array(
- 'api.php?action=query&list=backlinks&bltitle=Main%20Page',
- 'api.php?action=query&generator=backlinks&gbltitle=Main%20Page&prop=info'
+ 'action=query&list=backlinks&bltitle=Main%20Page'
+ => 'apihelp-query+backlinks-example-simple',
+ 'action=query&generator=backlinks&gbltitle=Main%20Page&prop=info'
+ => 'apihelp-query+backlinks-example-generator',
),
'embeddedin' => array(
- 'api.php?action=query&list=embeddedin&eititle=Template:Stub',
- 'api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info'
+ 'action=query&list=embeddedin&eititle=Template:Stub'
+ => 'apihelp-query+embeddedin-example-simple',
+ 'action=query&generator=embeddedin&geititle=Template:Stub&prop=info'
+ => 'apihelp-query+embeddedin-example-generator',
),
'imageusage' => array(
- 'api.php?action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg',
- 'api.php?action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info'
+ 'action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg'
+ => 'apihelp-query+imageusage-example-simple',
+ 'action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info'
+ => 'apihelp-query+imageusage-example-generator',
)
);
diff --git a/includes/api/ApiQueryBacklinksprop.php b/includes/api/ApiQueryBacklinksprop.php
index cd682612..8e271e7b 100644
--- a/includes/api/ApiQueryBacklinksprop.php
+++ b/includes/api/ApiQueryBacklinksprop.php
@@ -40,15 +40,13 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
'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',
+ 'fragment',
),
'showredirects' => false,
'show' => array(
- 'fragment' => 'Only show redirects with a fragment',
- '!fragment' => 'Only show redirects without a fragment',
+ 'fragment',
+ '!fragment',
),
),
'linkshere' => array(
@@ -56,8 +54,6 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
'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(
@@ -65,8 +61,6 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
'prefix' => 'tl',
'linktable' => 'templatelinks',
'from_namespace' => true,
- 'what' => 'pages transcluding',
- 'description' => 'Find all pages that transclude the given pages.',
'showredirects' => true,
),
'fileusage' => array(
@@ -75,9 +69,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
'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,
),
);
@@ -106,8 +98,8 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
$emptyString = $db->addQuotes( '' );
$pageSet = $this->getPageSet();
- $titles = $pageSet->getGoodTitles() + $pageSet->getMissingTitles();
- $map = $pageSet->getAllTitlesByNamespace();
+ $titles = $pageSet->getGoodAndMissingTitles();
+ $map = $pageSet->getGoodAndMissingTitlesByNamespace();
// Determine our fields to query on
$p = $settings['prefix'];
@@ -295,8 +287,8 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
if ( $fld_fragment && $row->rd_fragment !== null && $row->rd_fragment !== '' ) {
$vals['fragment'] = $row->rd_fragment;
}
- if ( $fld_redirect && $row->page_is_redirect ) {
- $vals['redirect'] = '';
+ if ( $fld_redirect ) {
+ $vals['redirect'] = (bool)$row->page_is_redirect;
}
$fit = $this->addPageSubItem( $id, $vals );
if ( !$fit ) {
@@ -348,6 +340,7 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => 'namespace',
),
+ 'show' => null, // Will be filled/removed below
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -355,16 +348,24 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
);
+ if ( empty( $settings['from_namespace'] ) && $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+ 'api-help-param-limited-in-miser-mode',
+ );
+ }
+
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'] )
+ $ret['prop'][ApiBase::PARAM_TYPE], $settings['props']
);
}
@@ -374,93 +375,32 @@ class ApiQueryBacklinksprop extends ApiQueryGeneratorBase {
$show[] = '!redirect';
}
if ( isset( $settings['show'] ) ) {
- $show = array_merge( $show, array_keys( $settings['show'] ) );
+ $show = array_merge( $show, $settings['show'] );
}
if ( $show ) {
$ret['show'] = array(
ApiBase::PARAM_TYPE => $show,
ApiBase::PARAM_ISMULTI => true,
);
+ } else {
+ unset( $ret['show'] );
}
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() {
+ protected function getExamplesMessages() {
$settings = self::$settings[$this->getModuleName()];
$name = $this->getModuleName();
- $what = $settings['what'];
+ $path = $this->getModulePath();
$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]]",
+ "action=query&prop={$name}&titles={$etitle}"
+ => "apihelp-$path-example-simple",
+ "action=query&generator={$name}&titles={$etitle}&prop=info"
+ => "apihelp-$path-example-generator",
);
}
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 65e10ab7..a15754ce 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -70,6 +70,10 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Override this method to request extra fields from the pageSet
* using $pageSet->requestField('fieldName')
+ *
+ * Note this only makes sense for 'prop' modules, as 'list' and 'meta'
+ * modules should not be using the pageset.
+ *
* @param ApiPageSet $pageSet
*/
public function requestExtraData( $pageSet ) {
@@ -91,6 +95,13 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
+ * @see ApiBase::getParent()
+ */
+ public function getParent() {
+ return $this->getQuery();
+ }
+
+ /**
* Get the Query database connection (read-only)
* @return DatabaseBase
*/
@@ -361,12 +372,7 @@ abstract class ApiQueryBase extends ApiBase {
isset( $extraQuery['join_conds'] ) ? (array)$extraQuery['join_conds'] : array()
);
- // getDB has its own profileDBIn/Out calls
- $db = $this->getDB();
-
- $this->profileDBIn();
- $res = $db->select( $tables, $fields, $where, $method, $options, $join_conds );
- $this->profileDBOut();
+ $res = $this->getDB()->select( $tables, $fields, $where, $method, $options, $join_conds );
return $res;
}
@@ -450,7 +456,7 @@ abstract class ApiQueryBase extends ApiBase {
*/
protected function addPageSubItems( $pageId, $data ) {
$result = $this->getResult();
- $result->setIndexedTagName( $data, $this->getModulePrefix() );
+ ApiResult::setIndexedTagName( $data, $this->getModulePrefix() );
return $result->addValue( array( 'query', 'pages', intval( $pageId ) ),
$this->getModuleName(),
@@ -475,7 +481,7 @@ abstract class ApiQueryBase extends ApiBase {
if ( !$fit ) {
return false;
}
- $result->setIndexedTagName_internal( array( 'query', 'pages', $pageId,
+ $result->addIndexedTagName( array( 'query', 'pages', $pageId,
$this->getModuleName() ), $elemname );
return true;
@@ -487,7 +493,7 @@ abstract class ApiQueryBase extends ApiBase {
* @param string|array $paramValue Parameter value
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
- $this->getResult()->setContinueParam( $this, $paramName, $paramValue );
+ $this->getContinuationManager()->addContinueParam( $this, $paramName, $paramValue );
}
/**
@@ -502,7 +508,8 @@ abstract class ApiQueryBase extends ApiBase {
*/
public function titlePartToKey( $titlePart, $namespace = NS_MAIN ) {
$t = Title::makeTitleSafe( $namespace, $titlePart . 'x' );
- if ( !$t ) {
+ if ( !$t || $t->hasFragment() ) {
+ // Invalid title (e.g. bad chars) or contained a '#'.
$this->dieUsageMsg( array( 'invalidtitle', $titlePart ) );
}
if ( $namespace != $t->getNamespace() || $t->isExternal() ) {
@@ -578,7 +585,6 @@ abstract class ApiQueryBase extends ApiBase {
protected function checkRowCount() {
wfDeprecated( __METHOD__, '1.24' );
$db = $this->getDB();
- $this->profileDBIn();
$rowcount = $db->estimateRowCount(
$this->tables,
$this->fields,
@@ -586,7 +592,6 @@ abstract class ApiQueryBase extends ApiBase {
__METHOD__,
$this->options
);
- $this->profileDBOut();
if ( $rowcount > $this->getConfig()->get( 'APIMaxDBRows' ) ) {
return false;
@@ -705,13 +710,24 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase {
*/
protected function setContinueEnumParameter( $paramName, $paramValue ) {
if ( $this->mGeneratorPageSet !== null ) {
- $this->getResult()->setGeneratorContinueParam( $this, $paramName, $paramValue );
+ $this->getContinuationManager()->addGeneratorContinueParam( $this, $paramName, $paramValue );
} else {
parent::setContinueEnumParameter( $paramName, $paramValue );
}
}
/**
+ * @see ApiBase::getHelpFlags()
+ *
+ * Corresponding messages: api-help-flag-generator
+ */
+ protected function getHelpFlags() {
+ $flags = parent::getHelpFlags();
+ $flags[] = 'generator';
+ return $flags;
+ }
+
+ /**
* Execute this module as a generator
* @param ApiPageSet $resultPageSet All output should be appended to this object
*/
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index 33b25fd9..4a7023b7 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -31,11 +31,6 @@
*/
class ApiQueryBlocks extends ApiQueryBase {
- /**
- * @var array
- */
- protected $usernames;
-
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'bk' );
}
@@ -62,12 +57,11 @@ class ApiQueryBlocks extends ApiQueryBase {
$result = $this->getResult();
$this->addTables( 'ipblocks' );
- $this->addFields( array( 'ipb_auto', 'ipb_id' ) );
+ $this->addFields( array( 'ipb_auto', 'ipb_id', 'ipb_timestamp' ) );
$this->addFieldsIf( array( 'ipb_address', 'ipb_user' ), $fld_user || $fld_userid );
$this->addFieldsIf( 'ipb_by_text', $fld_by );
$this->addFieldsIf( 'ipb_by', $fld_byid );
- $this->addFieldsIf( 'ipb_timestamp', $fld_timestamp );
$this->addFieldsIf( 'ipb_expiry', $fld_expiry );
$this->addFieldsIf( 'ipb_reason', $fld_reason );
$this->addFieldsIf( array( 'ipb_range_start', 'ipb_range_end' ), $fld_range );
@@ -102,10 +96,11 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereFld( 'ipb_id', $params['ids'] );
}
if ( isset( $params['users'] ) ) {
+ $usernames = array();
foreach ( (array)$params['users'] as $u ) {
- $this->prepareUsername( $u );
+ $usernames[] = $this->prepareUsername( $u );
}
- $this->addWhereFld( 'ipb_address', $this->usernames );
+ $this->addWhereFld( 'ipb_address', $usernames );
$this->addWhereFld( 'ipb_auto', 0 );
}
if ( isset( $params['ip'] ) ) {
@@ -192,7 +187,9 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" );
break;
}
- $block = array();
+ $block = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
if ( $fld_id ) {
$block['id'] = $row->ipb_id;
}
@@ -223,27 +220,13 @@ class ApiQueryBlocks extends ApiQueryBase {
}
if ( $fld_flags ) {
// For clarity, these flags use the same names as their action=block counterparts
- if ( $row->ipb_auto ) {
- $block['automatic'] = '';
- }
- if ( $row->ipb_anon_only ) {
- $block['anononly'] = '';
- }
- if ( $row->ipb_create_account ) {
- $block['nocreate'] = '';
- }
- if ( $row->ipb_enable_autoblock ) {
- $block['autoblock'] = '';
- }
- if ( $row->ipb_block_email ) {
- $block['noemail'] = '';
- }
- if ( $row->ipb_deleted ) {
- $block['hidden'] = '';
- }
- if ( $row->ipb_allow_usertalk ) {
- $block['allowusertalk'] = '';
- }
+ $block['automatic'] = (bool)$row->ipb_auto;
+ $block['anononly'] = (bool)$row->ipb_anon_only;
+ $block['nocreate'] = (bool)$row->ipb_create_account;
+ $block['autoblock'] = (bool)$row->ipb_enable_autoblock;
+ $block['noemail'] = (bool)$row->ipb_block_email;
+ $block['hidden'] = (bool)$row->ipb_deleted;
+ $block['allowusertalk'] = (bool)$row->ipb_allow_usertalk;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $block );
if ( !$fit ) {
@@ -251,7 +234,7 @@ class ApiQueryBlocks extends ApiQueryBase {
break;
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'block' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'block' );
}
protected function prepareUsername( $user ) {
@@ -264,10 +247,12 @@ class ApiQueryBlocks extends ApiQueryBase {
if ( $name === false ) {
$this->dieUsage( "User name {$user} is not valid", 'param_user' );
}
- $this->usernames[] = $name;
+ return $name;
}
public function getAllowedParams() {
+ $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
+
return array(
'start' => array(
ApiBase::PARAM_TYPE => 'timestamp'
@@ -280,7 +265,8 @@ class ApiQueryBlocks extends ApiQueryBase {
'newer',
'older'
),
- ApiBase::PARAM_DFLT => 'older'
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
),
'ids' => array(
ApiBase::PARAM_TYPE => 'integer',
@@ -289,7 +275,13 @@ class ApiQueryBlocks extends ApiQueryBase {
'users' => array(
ApiBase::PARAM_ISMULTI => true
),
- 'ip' => null,
+ 'ip' => array(
+ ApiBase::PARAM_HELP_MSG => array(
+ 'apihelp-query+blocks-param-ip',
+ $blockCIDRLimit['IPv4'],
+ $blockCIDRLimit['IPv6'],
+ ),
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -326,56 +318,18 @@ class ApiQueryBlocks extends ApiQueryBase {
),
ApiBase::PARAM_ISMULTI => true
),
- 'continue' => null,
- );
- }
-
- public function getParamDescription() {
- $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
- $p = $this->getModulePrefix();
-
- return array(
- 'start' => 'The timestamp to start enumerating from',
- 'end' => 'The timestamp to stop enumerating at',
- 'dir' => $this->getDirectionDescription( $p ),
- 'ids' => 'List of block IDs to list (optional)',
- 'users' => 'List of users to search for (optional)',
- '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/{$blockCIDRLimit['IPv4']} or IPv6/{$blockCIDRLimit['IPv6']} " .
- "are not accepted"
- ),
- 'limit' => 'The maximum amount of blocks to list',
- 'prop' => array(
- 'Which properties to get',
- ' id - Adds the ID of the block',
- ' user - Adds the username of the blocked user',
- ' userid - Adds the user ID of the blocked user',
- ' by - Adds the username of the blocking user',
- ' byid - Adds the user ID of the blocking user',
- ' timestamp - Adds the timestamp of when the block was given',
- ' expiry - Adds the timestamp of when the block expires',
- ' reason - Adds the reason given for the block',
- ' range - Adds the range of IPs affected by the block',
- ' flags - Tags the ban with (autoblock, anononly, etc)',
- ),
- 'show' => array(
- 'Show only items that meet this criteria.',
- "For example, to see only indefinite blocks on IPs, set {$p}show=ip|!temp"
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- 'continue' => 'When more results are available, use this to continue',
);
}
- public function getDescription() {
- return 'List all blocked users and IP addresses.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=blocks',
- 'api.php?action=query&list=blocks&bkusers=Alice|Bob'
+ 'action=query&list=blocks'
+ => 'apihelp-query+blocks-example-simple',
+ 'action=query&list=blocks&bkusers=Alice|Bob'
+ => 'apihelp-query+blocks-example-users',
);
}
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index 1926dd09..35fa56ef 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -117,8 +117,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
}
- $this->addOption( 'USE INDEX', array( 'categorylinks' => 'cl_from' ) );
-
$sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
// Don't order by cl_from if it's constant in the WHERE clause
if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
@@ -152,8 +150,8 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
if ( isset( $prop['timestamp'] ) ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp );
}
- if ( isset( $prop['hidden'] ) && !is_null( $row->pp_propname ) ) {
- $vals['hidden'] = '';
+ if ( isset( $prop['hidden'] ) ) {
+ $vals['hidden'] = !is_null( $row->pp_propname );
}
$fit = $this->addPageSubItem( $row->cl_from, $vals );
@@ -202,7 +200,9 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'categories' => array(
ApiBase::PARAM_ISMULTI => true,
),
@@ -216,34 +216,12 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- 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',
- ' 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',
- 'dir' => 'The direction in which to list',
- );
- }
-
- public function getDescription() {
- return 'List all categories the page(s) belong to.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
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]]',
+ 'action=query&prop=categories&titles=Albert%20Einstein'
+ => 'apihelp-query+categories-example-simple',
+ 'action=query&generator=categories&titles=Albert%20Einstein&prop=info'
+ => 'apihelp-query+categories-example-generator',
);
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index 6e9f33c1..9f6c6044 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -38,14 +38,13 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
- $alltitles = $this->getPageSet()->getAllTitlesByNamespace();
+ $alltitles = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
if ( empty( $alltitles[NS_CATEGORY] ) ) {
return;
}
$categories = $alltitles[NS_CATEGORY];
- $titles = $this->getPageSet()->getGoodTitles() +
- $this->getPageSet()->getMissingTitles();
+ $titles = $this->getPageSet()->getGoodAndMissingTitles();
$cattitles = array();
foreach ( $categories as $c ) {
/** @var $t Title */
@@ -87,9 +86,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
$vals['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files;
$vals['files'] = intval( $row->cat_files );
$vals['subcats'] = intval( $row->cat_subcats );
- if ( $row->cat_hidden ) {
- $vals['hidden'] = '';
- }
+ $vals['hidden'] = (bool)$row->cat_hidden;
$fit = $this->addPageSubItems( $catids[$row->cat_title], $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $row->cat_title );
@@ -104,24 +101,19 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
public function getAllowedParams() {
return array(
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
);
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
return array(
- 'continue' => 'When more results are available, use this to continue',
+ 'action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar'
+ => 'apihelp-query+categoryinfo-example-simple',
);
}
- public function getDescription() {
- return 'Returns information about the given categories.';
- }
-
- public function getExamples() {
- return 'api.php?action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar';
- }
-
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Properties#categoryinfo_.2F_ci';
}
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index a88a9cb1..ec0c1d14 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -48,6 +48,15 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
/**
+ * @param string $hexSortkey
+ * @return bool
+ */
+ private function validateHexSortkey( $hexSortkey ) {
+ // A hex sortkey has an unbound number of 2 letter pairs
+ return preg_match( '/^(?:[a-fA-F0-9]{2})*$/', $hexSortkey );
+ }
+
+ /**
* @param ApiPageSet $resultPageSet
* @return void
*/
@@ -128,6 +137,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$queryTypes = array_slice( $queryTypes, $contTypeIndex );
// Add a WHERE clause for sortkey and from
+ $this->dieContinueUsageIf( !$this->validateHexSortkey( $cont[1] ) );
// pack( "H*", $foo ) is used to convert hex back to binary
$escSortkey = $this->getDB()->addQuotes( pack( 'H*', $cont[1] ) );
$from = intval( $cont[2] );
@@ -143,6 +153,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
if ( $params['startsortkeyprefix'] !== null ) {
$startsortkey = Collation::singleton()->getSortkey( $params['startsortkeyprefix'] );
} elseif ( $params['starthexsortkey'] !== null ) {
+ if ( !$this->validateHexSortkey( $params['starthexsortkey'] ) ) {
+ $this->dieUsage( 'The starthexsortkey provided is not valid', 'bad_starthexsortkey' );
+ }
$startsortkey = pack( 'H*', $params['starthexsortkey'] );
} else {
$this->logFeatureUsage( 'list=categorymembers&cmstartsortkey' );
@@ -151,6 +164,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
if ( $params['endsortkeyprefix'] !== null ) {
$endsortkey = Collation::singleton()->getSortkey( $params['endsortkeyprefix'] );
} elseif ( $params['endhexsortkey'] !== null ) {
+ if ( !$this->validateHexSortkey( $params['endhexsortkey'] ) ) {
+ $this->dieUsage( 'The endhexsortkey provided is not valid', 'bad_endhexsortkey' );
+ }
$endsortkey = pack( 'H*', $params['endhexsortkey'] );
} else {
$this->logFeatureUsage( 'list=categorymembers&cmendsortkey' );
@@ -230,7 +246,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $vals = array();
+ $vals = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
if ( $fld_ids ) {
$vals['pageid'] = intval( $row->page_id );
}
@@ -269,13 +287,13 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ), 'cm' );
}
}
public function getAllowedParams() {
- return array(
+ $ret = array(
'title' => array(
ApiBase::PARAM_TYPE => 'string',
),
@@ -307,7 +325,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'file'
)
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_DFLT => 10,
@@ -351,67 +371,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
ApiBase::PARAM_DEPRECATED => true,
),
);
- }
-
- public function getParamDescription() {
- $p = $this->getModulePrefix();
- $desc = array(
- '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)',
- ' 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',
- ),
- 'namespace' => 'Only include pages in these namespaces',
- 'type' => "What type of category members to include. Ignored when {$p}sort=timestamp is set",
- 'sort' => 'Property to sort by',
- '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",
- '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 ( $this->getConfig()->get( 'MiserMode' ) ) {
- $desc['namespace'] = array(
- $desc['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.',
- "Note that you can use {$p}type=subcat or {$p}type=file instead of {$p}namespace=14 or 6.",
+ $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+ 'api-help-param-limited-in-miser-mode',
);
}
- return $desc;
- }
-
- public function getDescription() {
- return 'List all pages in a given category.';
+ return $ret;
}
- public function getExamples() {
+ protected function getExamplesMessages() {
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]]',
+ 'action=query&list=categorymembers&cmtitle=Category:Physics'
+ => 'apihelp-query+categorymembers-example-simple',
+ 'action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info'
+ => 'apihelp-query+categorymembers-example-generator',
);
}
diff --git a/includes/api/ApiQueryContributors.php b/includes/api/ApiQueryContributors.php
index 55ea4702..7e76db25 100644
--- a/includes/api/ApiQueryContributors.php
+++ b/includes/api/ApiQueryContributors.php
@@ -236,43 +236,16 @@ class ApiQueryContributors extends ApiQueryBase {
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'
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- '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() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&prop=contributors&titles=Main_Page',
+ 'action=query&prop=contributors&titles=Main_Page'
+ => 'apihelp-query+contributors-example-simple',
);
}
diff --git a/includes/api/ApiQueryDeletedRevisions.php b/includes/api/ApiQueryDeletedRevisions.php
new file mode 100644
index 00000000..26ae2668
--- /dev/null
+++ b/includes/api/ApiQueryDeletedRevisions.php
@@ -0,0 +1,304 @@
+<?php
+/**
+ * Created on Oct 3, 2014
+ *
+ * Copyright © 2014 Brad Jorsch "bjorsch@wikimedia.org"
+ *
+ * Heavily based on ApiQueryDeletedrevs,
+ * Copyright © 2007 Roan Kattouw "<Firstname>.<Lastname>@gmail.com"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Query module to enumerate deleted revisions for pages.
+ *
+ * @ingroup API
+ */
+class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
+
+ public function __construct( ApiQuery $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'drv' );
+ }
+
+ protected function run( ApiPageSet $resultPageSet = null ) {
+ $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'
+ );
+ }
+
+ $result = $this->getResult();
+ $pageSet = $this->getPageSet();
+ $pageMap = $pageSet->getGoodAndMissingTitlesByNamespace();
+ $pageCount = count( $pageSet->getGoodAndMissingTitles() );
+ $revCount = $pageSet->getRevisionCount();
+ if ( $revCount === 0 && $pageCount === 0 ) {
+ // Nothing to do
+ return;
+ }
+ if ( $revCount !== 0 && count( $pageSet->getDeletedRevisionIDs() ) === 0 ) {
+ // Nothing to do, revisions were supplied but none are deleted
+ return;
+ }
+
+ $params = $this->extractRequestParams( false );
+
+ $db = $this->getDB();
+
+ if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
+ $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
+ }
+
+ $this->addTables( 'archive' );
+ if ( $resultPageSet === null ) {
+ $this->parseParameters( $params );
+ $this->addFields( Revision::selectArchiveFields() );
+ $this->addFields( array( 'ar_title', 'ar_namespace' ) );
+ } else {
+ $this->limit = $this->getParameter( 'limit' ) ?: 10;
+ $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id' ) );
+ }
+
+ if ( $this->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 ( $this->fetchContent ) {
+ // 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.
+ $this->addTables( 'text' );
+ $this->addJoinConds(
+ array( 'text' => array( 'LEFT JOIN', array( 'ar_text_id=old_id' ) ) )
+ );
+ $this->addFields( array( 'ar_text', 'ar_flags', 'old_text', 'old_flags' ) );
+
+ // This also means stricter restrictions
+ if ( !$user->isAllowedAny( 'undelete', 'deletedtext' ) ) {
+ $this->dieUsage(
+ 'You don\'t have permission to view deleted revision content',
+ 'permissiondenied'
+ );
+ }
+ }
+
+ $dir = $params['dir'];
+
+ if ( $revCount !== 0 ) {
+ $this->addWhere( array(
+ 'ar_rev_id' => array_keys( $pageSet->getDeletedRevisionIDs() )
+ ) );
+ } else {
+ // We need a custom WHERE clause that matches all titles.
+ $lb = new LinkBatch( $pageSet->getGoodAndMissingTitles() );
+ $where = $lb->constructSet( 'ar', $db );
+ $this->addWhere( $where );
+ }
+
+ if ( !is_null( $params['user'] ) ) {
+ $this->addWhereFld( 'ar_user_text', $params['user'] );
+ } elseif ( !is_null( $params['excludeuser'] ) ) {
+ $this->addWhere( 'ar_user_text != ' .
+ $db->addQuotes( $params['excludeuser'] ) );
+ }
+
+ 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'] );
+ $op = ( $dir == 'newer' ? '>' : '<' );
+ if ( $revCount !== 0 ) {
+ $this->dieContinueUsageIf( count( $cont ) != 2 );
+ $rev = intval( $cont[0] );
+ $this->dieContinueUsageIf( strval( $rev ) !== $cont[0] );
+ $ar_id = (int)$cont[1];
+ $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[1] );
+ $this->addWhere( "ar_rev_id $op $rev OR " .
+ "(ar_rev_id = $rev AND " .
+ "ar_id $op= $ar_id)" );
+ } else {
+ $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 OR " .
+ "(ar_timestamp = $ts AND " .
+ "ar_id $op= $ar_id)))))" );
+ }
+ }
+
+ $this->addOption( 'LIMIT', $this->limit + 1 );
+
+ if ( $revCount !== 0 ) {
+ // Sort by ar_rev_id when querying by ar_rev_id
+ $this->addWhereRange( 'ar_rev_id', $dir, null, null );
+ } else {
+ // Sort by ns and title in the same order as timestamp for efficiency
+ // But only when not already unique in the query
+ if ( count( $pageMap ) > 1 ) {
+ $this->addWhereRange( 'ar_namespace', $dir, null, null );
+ }
+ $oneTitle = key( reset( $pageMap ) );
+ foreach ( $pageMap as $pages ) {
+ if ( count( $pages ) > 1 || key( $pages ) !== $oneTitle ) {
+ $this->addWhereRange( 'ar_title', $dir, null, null );
+ break;
+ }
+ }
+ $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__ );
+ $count = 0;
+ $generated = array();
+ foreach ( $res as $row ) {
+ if ( ++$count > $this->limit ) {
+ // We've had enough
+ $this->setContinueEnumParameter( 'continue',
+ $revCount
+ ? "$row->ar_rev_id|$row->ar_id"
+ : "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ break;
+ }
+
+ if ( $resultPageSet !== null ) {
+ $generated[] = $row->ar_rev_id;
+ } else {
+ if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
+ // Was it converted?
+ $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+ $converted = $pageSet->getConvertedTitles();
+ if ( $title && isset( $converted[$title->getPrefixedText()] ) ) {
+ $title = Title::newFromText( $converted[$title->getPrefixedText()] );
+ if ( $title && isset( $pageMap[$title->getNamespace()][$title->getDBkey()] ) ) {
+ $pageMap[$row->ar_namespace][$row->ar_title] =
+ $pageMap[$title->getNamespace()][$title->getDBkey()];
+ }
+ }
+ }
+ if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
+ ApiBase::dieDebug( "Found row in archive (ar_id={$row->ar_id}) that didn't " .
+ "get processed by ApiPageSet" );
+ }
+
+ $fit = $this->addPageSubItem(
+ $pageMap[$row->ar_namespace][$row->ar_title],
+ $this->extractRevisionInfo( Revision::newFromArchiveRow( $row ), $row ),
+ 'rev'
+ );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue',
+ $revCount
+ ? "$row->ar_rev_id|$row->ar_id"
+ : "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
+ );
+ break;
+ }
+ }
+ }
+
+ if ( $resultPageSet !== null ) {
+ $resultPageSet->populateFromRevisionIDs( $generated );
+ }
+ }
+
+ public function getAllowedParams() {
+ return parent::getAllowedParams() + array(
+ 'start' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ),
+ 'end' => array(
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ),
+ 'dir' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'newer',
+ 'older'
+ ),
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
+ ),
+ 'tag' => null,
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'excludeuser' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
+ );
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=query&prop=deletedrevisions&titles=Main%20Page|Talk:Main%20Page&' .
+ 'drvprop=user|comment|content'
+ => 'apihelp-query+deletedrevisions-example-titles',
+ 'action=query&prop=deletedrevisions&revids=123456'
+ => 'apihelp-query+deletedrevisions-example-revids',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#deletedrevisions_.2F_drv';
+ }
+}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index 9042696b..72a331f1 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -28,6 +28,7 @@
* Query module to enumerate all deleted revisions.
*
* @ingroup API
+ * @deprecated since 1.25
*/
class ApiQueryDeletedrevs extends ApiQueryBase {
@@ -45,6 +46,12 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
);
}
+ $this->setWarning(
+ 'list=deletedrevs has been deprecated. Please use prop=deletedrevisions or ' .
+ 'list=alldeletedrevisions instead.'
+ );
+ $this->logFeatureUsage( 'action=query&list=deletedrevs' );
+
$db = $this->getDB();
$params = $this->extractRequestParams( false );
$prop = array_flip( $params['prop'] );
@@ -68,8 +75,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
);
}
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
$fld_token = false;
}
@@ -169,7 +177,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $limit == 'max' ) {
$limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
+ $this->getResult()->addParsedLimit( $this->getModuleName(), $limit );
}
$this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
@@ -312,7 +320,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
if ( $fld_user || $fld_userid ) {
if ( $row->ar_deleted & Revision::DELETED_USER ) {
- $rev['userhidden'] = '';
+ $rev['userhidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_USER, $user ) ) {
@@ -327,7 +335,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_comment || $fld_parsedcomment ) {
if ( $row->ar_deleted & Revision::DELETED_COMMENT ) {
- $rev['commenthidden'] = '';
+ $rev['commenthidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_COMMENT, $user ) ) {
@@ -341,15 +349,15 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
}
- if ( $fld_minor && $row->ar_minor_edit == 1 ) {
- $rev['minor'] = '';
+ if ( $fld_minor ) {
+ $rev['minor'] = $row->ar_minor_edit == 1;
}
if ( $fld_len ) {
$rev['len'] = $row->ar_len;
}
if ( $fld_sha1 ) {
if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
- $rev['sha1hidden'] = '';
+ $rev['sha1hidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
@@ -362,15 +370,15 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
if ( $fld_content ) {
if ( $row->ar_deleted & Revision::DELETED_TEXT ) {
- $rev['texthidden'] = '';
+ $rev['texthidden'] = true;
$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_' ) );
+ ApiResult::setContentValue( $rev, 'text', Revision::getRevisionText( $row, 'ar_' ) );
} else {
- ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
+ ApiResult::setContentValue( $rev, 'text', Revision::getRevisionText( $row ) );
}
}
}
@@ -378,7 +386,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_tags ) {
if ( $row->ts_tags ) {
$tags = explode( ',', $row->ts_tags );
- $this->getResult()->setIndexedTagName( $tags, 'tag' );
+ ApiResult::setIndexedTagName( $tags, 'tag' );
$rev['tags'] = $tags;
} else {
$rev['tags'] = array();
@@ -386,14 +394,14 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
if ( $anyHidden && ( $row->ar_deleted & Revision::DELETED_RESTRICTED ) ) {
- $rev['suppressed'] = '';
+ $rev['suppressed'] = true;
}
if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
$pageID = $newPageID++;
$pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
$a['revisions'] = array( $rev );
- $result->setIndexedTagName( $a['revisions'], 'rev' );
+ ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
$title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
ApiQueryBase::addTitleInfo( $a, $title );
if ( $fld_token ) {
@@ -417,46 +425,56 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
break;
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
+ }
+
+ public function isDeprecated() {
+ return true;
}
public function getAllowedParams() {
return array(
'start' => array(
- ApiBase::PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 1, 2 ) ),
),
'end' => array(
ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 1, 2 ) ),
),
'dir' => array(
ApiBase::PARAM_TYPE => array(
'newer',
'older'
),
- ApiBase::PARAM_DFLT => 'older'
+ ApiBase::PARAM_DFLT => 'older',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 1, 3 ) ),
),
- 'from' => null,
- 'to' => null,
- 'prefix' => null,
- 'continue' => null,
- 'unique' => false,
- 'tag' => null,
- 'user' => array(
- ApiBase::PARAM_TYPE => 'user'
+ 'from' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 3 ) ),
),
- 'excludeuser' => array(
- ApiBase::PARAM_TYPE => 'user'
+ 'to' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 3 ) ),
+ ),
+ 'prefix' => array(
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 3 ) ),
+ ),
+ 'unique' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 3 ) ),
),
'namespace' => array(
ApiBase::PARAM_TYPE => 'namespace',
ApiBase::PARAM_DFLT => NS_MAIN,
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'modes', 3 ) ),
),
- '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
+ 'tag' => null,
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
+ 'excludeuser' => array(
+ ApiBase::PARAM_TYPE => 'user'
),
'prop' => array(
ApiBase::PARAM_DFLT => 'user|comment',
@@ -476,68 +494,30 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
),
ApiBase::PARAM_ISMULTI => true
),
- );
- }
-
- public function getParamDescription() {
- return array(
- 'start' => 'The timestamp to start enumerating from (1, 2)',
- 'end' => 'The timestamp to stop enumerating at (1, 2)',
- 'dir' => $this->getDirectionDescription( $this->getModulePrefix(), ' (1, 3)' ),
- 'from' => 'Start listing at this title (3)',
- 'to' => 'Stop listing at this title (3)',
- 'prefix' => 'Search for all page titles that begin with this value (3)',
- 'limit' => 'The maximum amount of revisions to list',
- 'prop' => array(
- 'Which properties to get',
- ' revid - Adds the revision ID of the deleted revision',
- ' parentid - Adds the revision ID of the previous revision to the page',
- ' user - Adds the user who made the revision',
- ' userid - Adds the user ID whom made the revision',
- ' comment - Adds the comment of the revision',
- ' parsedcomment - Adds the parsed comment of the revision',
- ' minor - Tags if the revision is minor',
- ' 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 - DEPRECATED! Gives the edit token',
- ' tags - Tags for the revision',
+ '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' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- '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',
- 'unique' => 'List only one revision for each page (3)',
- '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).",
- '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.',
);
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&' .
+ '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)',
- 'api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50'
- => 'List the first 50 deleted revisions in the main namespace (mode 3)',
- 'api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique='
- => 'List the first 50 deleted pages in the Talk namespace (mode 3):',
+ => 'apihelp-query+deletedrevs-example-mode1',
+ 'action=query&list=deletedrevs&druser=Bob&drlimit=50'
+ => 'apihelp-query+deletedrevs-example-mode2',
+ 'action=query&list=deletedrevs&drdir=newer&drlimit=50'
+ => 'apihelp-query+deletedrevs-example-mode3-main',
+ 'action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique='
+ => 'apihelp-query+deletedrevs-example-mode3-talk',
);
}
diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php
index cf0d841e..a6509d41 100644
--- a/includes/api/ApiQueryDisabled.php
+++ b/includes/api/ApiQueryDisabled.php
@@ -44,17 +44,7 @@ class ApiQueryDisabled extends ApiQueryBase {
return array();
}
- public function getParamDescription() {
- return array();
- }
-
- public function getDescription() {
- return array(
- 'This module has been disabled.'
- );
- }
-
- public function getExamples() {
- return array();
+ public function getDescriptionMessage() {
+ return 'apihelp-query+disabled-description';
}
}
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index 6d836cd5..4d0bcfed 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -52,7 +52,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
*/
private function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
- $namespaces = $this->getPageSet()->getAllTitlesByNamespace();
+ $namespaces = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
if ( empty( $namespaces[NS_FILE] ) ) {
return;
}
@@ -137,11 +137,9 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$r = array(
'name' => $dupName,
'user' => $dupFile->getUser( 'text' ),
- 'timestamp' => wfTimestamp( TS_ISO_8601, $dupFile->getTimestamp() )
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $dupFile->getTimestamp() ),
+ 'shared' => !$dupFile->isLocal(),
);
- if ( !$dupFile->isLocal() ) {
- $r['shared'] = '';
- }
$fit = $this->addPageSubItem( $pageId, $r );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $image . '|' . $dupName );
@@ -167,7 +165,9 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'dir' => array(
ApiBase::PARAM_DFLT => 'ascending',
ApiBase::PARAM_TYPE => array(
@@ -179,23 +179,12 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- return array(
- 'limit' => 'How many duplicate files to return',
- 'continue' => 'When more results are available, use this to continue',
- 'dir' => 'The direction in which to list',
- 'localonly' => 'Look only for files in the local repository',
- );
- }
-
- public function getDescription() {
- return 'List all files that are duplicates of the given file(s) based on hash values.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles',
- 'api.php?action=query&generator=allimages&prop=duplicatefiles',
+ 'action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles'
+ => 'apihelp-query+duplicatefiles-example-simple',
+ 'action=query&generator=allimages&prop=duplicatefiles'
+ => 'apihelp-query+duplicatefiles-example-generated',
);
}
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index faabb920..3f65a19e 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -112,7 +112,9 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $vals = array();
+ $vals = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
if ( $fld_ids ) {
$vals['pageid'] = intval( $row->page_id );
}
@@ -139,13 +141,13 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ),
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ),
$this->getModulePrefix() );
}
}
public function getAllowedParams() {
- return array(
+ $ret = array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'ids|title|url',
@@ -156,7 +158,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
)
),
'offset' => array(
- ApiBase::PARAM_TYPE => 'integer'
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
'protocol' => array(
ApiBase::PARAM_TYPE => self::prepareProtocols(),
@@ -176,6 +179,14 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
),
'expandurl' => false,
);
+
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['namespace'][ApiBase::PARAM_HELP_MSG_APPEND] = array(
+ 'api-help-param-limited-in-miser-mode',
+ );
+ }
+
+ return $ret;
}
public static function prepareProtocols() {
@@ -207,45 +218,10 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
}
- public function getParamDescription() {
- $p = $this->getModulePrefix();
- $desc = array(
- 'prop' => array(
- 'What pieces of information to include',
- ' ids - Adds the ID of page',
- ' title - Adds the title and namespace ID of the page',
- ' url - Adds the URL used in the page',
- ),
- 'offset' => 'Used for paging. Use the value returned for "continue"',
- 'protocol' => array(
- "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',
- 'namespace' => 'The page namespace(s) to enumerate.',
- 'limit' => 'How many pages to return.',
- 'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
- );
-
- if ( $this->getConfig()->get( 'MiserMode' ) ) {
- $desc['namespace'] = array(
- $desc['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',
- );
- }
-
- return $desc;
- }
-
- public function getDescription() {
- return 'Enumerate pages that contain a given URL.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=exturlusage&euquery=www.mediawiki.org'
+ 'action=query&list=exturlusage&euquery=www.mediawiki.org'
+ => 'apihelp-query+exturlusage-example-simple',
);
}
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index 95666354..ec3d9d27 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -91,7 +91,7 @@ class ApiQueryExternalLinks extends ApiQueryBase {
if ( $params['expandurl'] ) {
$to = wfExpandUrl( $to, PROTO_CANONICAL );
}
- ApiResult::setContent( $entry, $to );
+ ApiResult::setContentValue( $entry, 'url', $to );
$fit = $this->addPageSubItem( $row->el_from, $entry );
if ( !$fit ) {
$this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
@@ -114,7 +114,8 @@ class ApiQueryExternalLinks extends ApiQueryBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'offset' => array(
- ApiBase::PARAM_TYPE => 'integer'
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
'protocol' => array(
ApiBase::PARAM_TYPE => ApiQueryExtLinksUsage::prepareProtocols(),
@@ -125,30 +126,10 @@ 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',
- 'protocol' => array(
- "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',
- 'expandurl' => 'Expand protocol-relative URLs with the canonical protocol',
- );
- }
-
- public function getDescription() {
- return 'Returns all external URLs (not interwikis) from the given page(s).';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&prop=extlinks&titles=Main%20Page'
- => 'Get a list of external links on the [[Main Page]]',
+ 'action=query&prop=extlinks&titles=Main%20Page'
+ => 'apihelp-query+extlinks-example-simple',
);
}
diff --git a/includes/api/ApiQueryFileRepoInfo.php b/includes/api/ApiQueryFileRepoInfo.php
index d1600efe..9ad7e27e 100644
--- a/includes/api/ApiQueryFileRepoInfo.php
+++ b/includes/api/ApiQueryFileRepoInfo.php
@@ -55,7 +55,9 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
$repos[] = array_intersect_key( $repoGroup->getLocalRepo()->getInfo(), $props );
$result = $this->getResult();
- $result->setIndexedTagName( $repos, 'repo' );
+ ApiResult::setIndexedTagName( $repos, 'repo' );
+ ApiResult::setArrayTypeRecursive( $repos, 'assoc' );
+ ApiResult::setArrayType( $repos, 'array' );
$result->addValue( array( 'query' ), 'repos', $repos );
}
@@ -89,27 +91,10 @@ class ApiQueryFileRepoInfo extends ApiQueryBase {
) ) );
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
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.',
- ' 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.',
- ),
- );
- }
-
- public function getDescription() {
- return 'Return meta information about image repositories configured on the wiki.';
- }
-
- public function getExamples() {
- return array(
- 'api.php?action=query&meta=filerepoinfo&friprop=apiurl|name|displayname',
+ 'action=query&meta=filerepoinfo&friprop=apiurl|name|displayname'
+ => 'apihelp-query+filerepoinfo-example-simple',
);
}
}
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index f047d8d4..4d357a7f 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -193,7 +193,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
$pageCount = ArchivedFile::newFromRow( $row )->pageCount();
if ( $pageCount !== false ) {
- $vals['pagecount'] = $pageCount;
+ $file['pagecount'] = $pageCount;
}
$file['height'] = $row->fa_height;
@@ -218,17 +218,17 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
if ( $row->fa_deleted & File::DELETED_FILE ) {
- $file['filehidden'] = '';
+ $file['filehidden'] = true;
}
if ( $row->fa_deleted & File::DELETED_COMMENT ) {
- $file['commenthidden'] = '';
+ $file['commenthidden'] = true;
}
if ( $row->fa_deleted & File::DELETED_USER ) {
- $file['userhidden'] = '';
+ $file['userhidden'] = true;
}
if ( $row->fa_deleted & File::DELETED_RESTRICTED ) {
// This file is deleted for normal admins
- $file['suppressed'] = '';
+ $file['suppressed'] = true;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
@@ -240,22 +240,14 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'fa' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'fa' );
}
public function getAllowedParams() {
return array(
'from' => null,
- 'continue' => null,
'to' => null,
'prefix' => null,
- '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
- ),
'dir' => array(
ApiBase::PARAM_DFLT => 'ascending',
ApiBase::PARAM_TYPE => array(
@@ -283,48 +275,23 @@ class ApiQueryFilearchive extends ApiQueryBase {
'archivename',
),
),
- );
- }
-
- public function getParamDescription() {
- return array(
- 'from' => 'The image title to start enumerating from',
- 'continue' => 'When more results are available, use this to continue',
- 'to' => 'The image title to stop enumerating at',
- 'prefix' => 'Search for all image titles that begin with this value',
- 'dir' => 'The direction in which to list',
- 'limit' => 'How many images to return in total',
- 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36",
- 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)',
- 'prop' => array(
- 'What image information to get:',
- ' 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)',
- ' dimensions - Alias for size',
- ' description - Adds description the image version',
- ' parseddescription - Parse the description on the version',
- ' mime - Adds MIME of the image',
- ' mediatype - Adds the media type of the image',
- ' metadata - Lists Exif metadata for the version of the image',
- ' bitdepth - Adds the bit depth of the version',
- ' archivename - Adds the file name of the archive version for non-latest versions'
+ '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' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
);
}
- public function getDescription() {
- return 'Enumerate all deleted files sequentially.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=filearchive' => array(
- 'Simple Use',
- 'Show a list of all deleted files',
- ),
+ 'action=query&list=filearchive'
+ => 'apihelp-query+filearchive-example-simple',
);
}
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index b5aa45bf..618387d2 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -132,7 +132,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
ApiQueryBase::addTitleInfo( $entry, $title );
if ( $row->page_is_redirect ) {
- $entry['redirect'] = '';
+ $entry['redirect'] = true;
}
if ( $iwprefix ) {
@@ -155,7 +155,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'iw' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'iw' );
} else {
$resultPageSet->populateFromTitles( $pages );
}
@@ -169,7 +169,9 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
return array(
'prefix' => null,
'title' => null,
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -195,33 +197,12 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- return array(
- 'prefix' => 'Prefix for the interwiki',
- 'title' => "Interwiki link to search for. Must be used with {$this->getModulePrefix()}prefix",
- 'continue' => 'When more results are available, use this to continue',
- 'prop' => array(
- 'Which properties to get',
- ' iwprefix - Adds the prefix of the interwiki',
- ' iwtitle - Adds the title of the interwiki',
- ),
- 'limit' => 'How many total pages to return',
- 'dir' => 'The direction in which to list',
- );
- }
-
- 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".',
- );
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=iwbacklinks&iwbltitle=Test&iwblprefix=wikibooks',
- 'api.php?action=query&generator=iwbacklinks&giwbltitle=Test&giwblprefix=wikibooks&prop=info'
+ 'action=query&list=iwbacklinks&iwbltitle=Test&iwblprefix=wikibooks'
+ => 'apihelp-query+iwbacklinks-example-simple',
+ 'action=query&generator=iwbacklinks&giwbltitle=Test&giwblprefix=wikibooks&prop=info'
+ => 'apihelp-query+iwbacklinks-example-generator',
);
}
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index a185ee24..aca3f700 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -129,7 +129,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
}
}
- ApiResult::setContent( $entry, $row->iwl_title );
+ ApiResult::setContentValue( $entry, 'title', $row->iwl_title );
$fit = $this->addPageSubItem( $row->iwl_from, $entry );
if ( !$fit ) {
$this->setContinueEnumParameter(
@@ -147,24 +147,12 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getAllowedParams() {
return array(
- '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',
- ApiBase::PARAM_MIN => 1,
- ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
- ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
- ),
- 'continue' => null,
'prefix' => null,
'title' => null,
'dir' => array(
@@ -174,32 +162,27 @@ class ApiQueryIWLinks extends ApiQueryBase {
'descending'
)
),
- );
- }
-
- public function getParamDescription() {
- return array(
- 'prop' => array(
- 'Which additional properties to get for each interlanguage link',
- ' url - Adds the full URL',
+ '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' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
+ 'url' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
),
- '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',
- 'title' => "Interwiki link to search for. Must be used with {$this->getModulePrefix()}prefix",
- 'dir' => 'The direction in which to list',
);
}
- public function getDescription() {
- return 'Returns all interwiki links from the given page(s).';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&prop=iwlinks&titles=Main%20Page'
- => 'Get interwiki links from the [[Main Page]]',
+ 'action=query&prop=iwlinks&titles=Main%20Page'
+ => 'apihelp-query+iwlinks-example-simple',
);
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index 945374b1..94b4bbdb 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -58,7 +58,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
'revdelUser' => $this->getUser(),
);
- $pageIds = $this->getPageSet()->getAllTitlesByNamespace();
+ $pageIds = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
if ( !empty( $pageIds[NS_FILE] ) ) {
$titles = array_keys( $pageIds[NS_FILE] );
asort( $titles ); // Ensure the order is always the same
@@ -373,7 +373,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
);
}
$version = $opts['version'];
- $vals = array();
+ $vals = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
// Timestamp is shown even if the file is revdelete'd in interface
// so do same here.
if ( isset( $prop['timestamp'] ) ) {
@@ -397,7 +399,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $user || $userid ) {
if ( $file->isDeleted( File::DELETED_USER ) ) {
- $vals['userhidden'] = '';
+ $vals['userhidden'] = true;
$anyHidden = true;
}
if ( $canShowField( File::DELETED_USER ) ) {
@@ -408,7 +410,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['userid'] = $file->getUser( 'id' );
}
if ( !$file->getUser( 'id' ) ) {
- $vals['anon'] = '';
+ $vals['anon'] = true;
}
}
}
@@ -438,7 +440,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
if ( $pcomment || $comment ) {
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
- $vals['commenthidden'] = '';
+ $vals['commenthidden'] = true;
$anyHidden = true;
}
if ( $canShowField( File::DELETED_COMMENT ) ) {
@@ -469,7 +471,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
if ( $file->isDeleted( File::DELETED_FILE ) ) {
- $vals['filehidden'] = '';
+ $vals['filehidden'] = true;
$anyHidden = true;
}
@@ -599,7 +601,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$retval[] = $r;
}
}
- $result->setIndexedTagName( $retval, 'metadata' );
+ ApiResult::setIndexedTagName( $retval, 'metadata' );
return $retval;
}
@@ -632,7 +634,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'timestamp|user',
- ApiBase::PARAM_TYPE => self::getPropertyNames()
+ ApiBase::PARAM_TYPE => self::getPropertyNames(),
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => self::getPropertyMessages(),
),
'limit' => array(
ApiBase::PARAM_TYPE => 'limit',
@@ -649,7 +652,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
),
'urlwidth' => array(
ApiBase::PARAM_TYPE => 'integer',
- ApiBase::PARAM_DFLT => -1
+ ApiBase::PARAM_DFLT => -1,
+ ApiBase::PARAM_HELP_MSG => array(
+ 'apihelp-query+imageinfo-param-urlwidth',
+ ApiQueryImageInfo::TRANSFORM_LIMIT,
+ ),
),
'urlheight' => array(
ApiBase::PARAM_TYPE => 'integer',
@@ -675,7 +682,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
ApiBase::PARAM_DFLT => '',
ApiBase::PARAM_TYPE => 'string',
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'localonly' => false,
);
}
@@ -684,16 +693,49 @@ class ApiQueryImageInfo extends ApiQueryBase {
* Returns all possible parameters to iiprop
*
* @param array $filter List of properties to filter out
- *
* @return array
*/
public static function getPropertyNames( $filter = array() ) {
- return array_diff( array_keys( self::getProperties() ), $filter );
+ return array_keys( self::getPropertyMessages( $filter ) );
+ }
+
+ /**
+ * Returns messages for all possible parameters to iiprop
+ *
+ * @param array $filter List of properties to filter out
+ * @return array
+ */
+ public static function getPropertyMessages( $filter = array() ) {
+ return array_diff_key(
+ array(
+ 'timestamp' => 'apihelp-query+imageinfo-paramvalue-prop-timestamp',
+ 'user' => 'apihelp-query+imageinfo-paramvalue-prop-user',
+ 'userid' => 'apihelp-query+imageinfo-paramvalue-prop-userid',
+ 'comment' => 'apihelp-query+imageinfo-paramvalue-prop-comment',
+ 'parsedcomment' => 'apihelp-query+imageinfo-paramvalue-prop-parsedcomment',
+ 'canonicaltitle' => 'apihelp-query+imageinfo-paramvalue-prop-canonicaltitle',
+ 'url' => 'apihelp-query+imageinfo-paramvalue-prop-url',
+ 'size' => 'apihelp-query+imageinfo-paramvalue-prop-size',
+ 'dimensions' => 'apihelp-query+imageinfo-paramvalue-prop-dimensions',
+ 'sha1' => 'apihelp-query+imageinfo-paramvalue-prop-sha1',
+ 'mime' => 'apihelp-query+imageinfo-paramvalue-prop-mime',
+ 'thumbmime' => 'apihelp-query+imageinfo-paramvalue-prop-thumbmime',
+ 'mediatype' => 'apihelp-query+imageinfo-paramvalue-prop-mediatype',
+ 'metadata' => 'apihelp-query+imageinfo-paramvalue-prop-metadata',
+ 'commonmetadata' => 'apihelp-query+imageinfo-paramvalue-prop-commonmetadata',
+ 'extmetadata' => 'apihelp-query+imageinfo-paramvalue-prop-extmetadata',
+ 'archivename' => 'apihelp-query+imageinfo-paramvalue-prop-archivename',
+ 'bitdepth' => 'apihelp-query+imageinfo-paramvalue-prop-bitdepth',
+ 'uploadwarning' => 'apihelp-query+imageinfo-paramvalue-prop-uploadwarning',
+ ),
+ array_flip( $filter )
+ );
}
/**
* Returns array key value pairs of properties and their descriptions
*
+ * @deprecated since 1.25
* @param string $modulePrefix
* @return array
*/
@@ -730,6 +772,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
/**
* Returns the descriptions for the properties provided by getPropertyNames()
*
+ * @deprecated since 1.25
* @param array $filter List of properties to filter out
* @param string $modulePrefix
* @return array
@@ -741,55 +784,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
);
}
- /**
- * Return the API documentation for the parameters.
- * @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.",
- 'For performance reasons if this option is used, ' .
- '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'."
- ),
- '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" ),
- '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',
- );
- }
-
- public function getDescription() {
- return 'Returns image information and upload history.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
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',
+ 'action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo'
+ => 'apihelp-query+imageinfo-example-simple',
+ 'action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&' .
+ 'iiend=2007-12-31T23:59:59Z&iiprop=timestamp|user|url'
+ => 'apihelp-query+imageinfo-example-dated',
);
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index 9bc3abed..029d945d 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -146,7 +146,9 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'images' => array(
ApiBase::PARAM_ISMULTI => true,
),
@@ -160,26 +162,12 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- 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.',
- 'dir' => 'The direction in which to list',
- );
- }
-
- public function getDescription() {
- return 'Returns all images contained on the given page(s).';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
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]]',
+ 'action=query&prop=images&titles=Main%20Page'
+ => 'apihelp-query+images-example-simple',
+ 'action=query&generator=images&titles=Main%20Page&prop=info'
+ => 'apihelp-query+images-example-generator',
);
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index d7037e3a..66178d4f 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -42,12 +42,14 @@ class ApiQueryInfo extends ApiQueryBase {
private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched,
$pageLatest, $pageLength;
- private $protections, $watched, $watchers, $notificationtimestamps,
+ private $protections, $restrictionTypes, $watched, $watchers, $notificationtimestamps,
$talkids, $subjectids, $displaytitles;
private $showZeroWatchers = false;
private $tokenFunctions;
+ private $countTestedActions = 0;
+
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'in' );
}
@@ -58,21 +60,21 @@ class ApiQueryInfo extends ApiQueryBase {
*/
public function requestExtraData( $pageSet ) {
$pageSet->requestField( 'page_restrictions' );
- // when resolving redirects, no page will have this field
- if ( !$pageSet->isResolvingRedirects() ) {
- $pageSet->requestField( 'page_is_redirect' );
- }
+ // If the pageset is resolving redirects we won't get page_is_redirect.
+ // But we can't know for sure until the pageset is executed (revids may
+ // turn it off), so request it unconditionally.
+ $pageSet->requestField( 'page_is_redirect' );
$pageSet->requestField( 'page_is_new' );
$config = $this->getConfig();
- if ( !$config->get( 'DisableCounters' ) ) {
- $pageSet->requestField( 'page_counter' );
- }
$pageSet->requestField( 'page_touched' );
$pageSet->requestField( 'page_latest' );
$pageSet->requestField( 'page_len' );
if ( $config->get( 'ContentHandlerUseDB' ) ) {
$pageSet->requestField( 'page_content_model' );
}
+ if ( $config->get( 'PageLanguageUseDB' ) ) {
+ $pageSet->requestField( 'page_lang' );
+ }
}
/**
@@ -88,8 +90,9 @@ class ApiQueryInfo extends ApiQueryBase {
return $this->tokenFunctions;
}
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
return array();
}
@@ -104,7 +107,7 @@ class ApiQueryInfo extends ApiQueryBase {
'import' => array( 'ApiQueryInfo', 'getImportToken' ),
'watch' => array( 'ApiQueryInfo', 'getWatchToken' ),
);
- wfRunHooks( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) );
+ Hooks::run( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
}
@@ -328,9 +331,6 @@ class ApiQueryInfo extends ApiQueryBase {
: array();
$this->pageIsNew = $pageSet->getCustomField( 'page_is_new' );
- if ( !$this->getConfig()->get( 'DisableCounters' ) ) {
- $this->pageCounter = $pageSet->getCustomField( 'page_counter' );
- }
$this->pageTouched = $pageSet->getCustomField( 'page_touched' );
$this->pageLatest = $pageSet->getCustomField( 'page_latest' );
$this->pageLength = $pageSet->getCustomField( 'page_len' );
@@ -360,7 +360,7 @@ class ApiQueryInfo extends ApiQueryBase {
/** @var $title Title */
foreach ( $this->everything as $pageid => $title ) {
$pageInfo = $this->extractPageInfo( $pageid, $title );
- $fit = $result->addValue( array(
+ $fit = $pageInfo !== null && $result->addValue( array(
'query',
'pages'
), $pageid, $pageInfo );
@@ -377,7 +377,7 @@ 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
- * @return array
+ * @return array|null
*/
private function extractPageInfo( $pageid, $title ) {
$pageInfo = array();
@@ -392,16 +392,13 @@ class ApiQueryInfo extends ApiQueryBase {
if ( $titleExists ) {
$pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
$pageInfo['lastrevid'] = intval( $this->pageLatest[$pageid] );
- $pageInfo['counter'] = $this->getConfig()->get( 'DisableCounters' )
- ? ''
- : intval( $this->pageCounter[$pageid] );
$pageInfo['length'] = intval( $this->pageLength[$pageid] );
if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
- $pageInfo['redirect'] = '';
+ $pageInfo['redirect'] = true;
}
if ( $this->pageIsNew[$pageid] ) {
- $pageInfo['new'] = '';
+ $pageInfo['new'] = true;
}
}
@@ -424,11 +421,18 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['protection'] =
$this->protections[$ns][$dbkey];
}
- $this->getResult()->setIndexedTagName( $pageInfo['protection'], 'pr' );
+ ApiResult::setIndexedTagName( $pageInfo['protection'], 'pr' );
+
+ $pageInfo['restrictiontypes'] = array();
+ if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
+ $pageInfo['restrictiontypes'] =
+ $this->restrictionTypes[$ns][$dbkey];
+ }
+ ApiResult::setIndexedTagName( $pageInfo['restrictiontypes'], 'rt' );
}
- if ( $this->fld_watched && isset( $this->watched[$ns][$dbkey] ) ) {
- $pageInfo['watched'] = '';
+ if ( $this->fld_watched ) {
+ $pageInfo['watched'] = isset( $this->watched[$ns][$dbkey] );
}
if ( $this->fld_watchers ) {
@@ -460,8 +464,8 @@ class ApiQueryInfo extends ApiQueryBase {
$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'] = '';
+ if ( $this->fld_readable ) {
+ $pageInfo['readable'] = $title->userCan( 'read', $this->getUser() );
}
if ( $this->fld_preload ) {
@@ -469,7 +473,7 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['preload'] = '';
} else {
$text = null;
- wfRunHooks( 'EditFormPreloadText', array( &$text, &$title ) );
+ Hooks::run( 'EditFormPreloadText', array( &$text, &$title ) );
$pageInfo['preload'] = $text;
}
@@ -483,6 +487,20 @@ class ApiQueryInfo extends ApiQueryBase {
}
}
+ if ( $this->params['testactions'] ) {
+ $limit = $this->getMain()->canApiHighLimits() ? self::LIMIT_SML1 : self::LIMIT_SML2;
+ if ( $this->countTestedActions >= $limit ) {
+ return null; // force a continuation
+ }
+
+ $user = $this->getUser();
+ $pageInfo['actions'] = array();
+ foreach ( $this->params['testactions'] as $action ) {
+ $this->countTestedActions++;
+ $pageInfo['actions'][$action] = $title->userCan( $action, $user );
+ }
+ }
+
return $pageInfo;
}
@@ -512,7 +530,7 @@ class ApiQueryInfo extends ApiQueryBase {
'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 )
);
if ( $row->pr_cascade ) {
- $a['cascade'] = '';
+ $a['cascade'] = true;
}
$this->protections[$title->getNamespace()][$title->getDBkey()][] = $a;
}
@@ -574,7 +592,8 @@ class ApiQueryInfo extends ApiQueryBase {
}
}
- // Cascading protections
+ // Separate good and missing titles into files and other pages
+ // and populate $this->restrictionTypes
$images = $others = array();
foreach ( $this->everything as $title ) {
if ( $title->getNamespace() == NS_FILE ) {
@@ -582,6 +601,9 @@ class ApiQueryInfo extends ApiQueryBase {
} else {
$others[] = $title;
}
+ // Applicable protection types
+ $this->restrictionTypes[$title->getNamespace()][$title->getDBkey()] =
+ array_values( $title->getRestrictionTypes() );
}
if ( count( $others ) ) {
@@ -817,45 +839,31 @@ class ApiQueryInfo extends ApiQueryBase {
'displaytitle',
// If you add more properties here, please consider whether they
// need to be added to getCacheMode()
- ) ),
+ ),
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => array(),
+ ),
+ 'testactions' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_DFLT => null,
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() )
),
- 'continue' => null,
- );
- }
-
- public function getParamDescription() {
- return array(
- 'prop' => array(
- 'Which additional properties to get:',
- ' protection - List the protection level of each page',
- ' talkid - The page ID of the talk page for each non-talk page',
- ' watched - List the watched status of each page',
- ' 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, 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',
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- 'token' => 'Request a token to perform a data-modifying action on a page',
- 'continue' => 'When more results are available, use this to continue',
);
}
- public function getDescription() {
- return 'Get basic page information such as namespace, title, last touched date, ...';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&prop=info&titles=Main%20Page',
- 'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page'
+ 'action=query&prop=info&titles=Main%20Page'
+ => 'apihelp-query+info-example-simple',
+ 'action=query&prop=info&inprop=protection&titles=Main%20Page'
+ => 'apihelp-query+info-example-protection',
);
}
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
index 34842c63..7be18b2f 100644
--- a/includes/api/ApiQueryLangBacklinks.php
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -131,7 +131,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
ApiQueryBase::addTitleInfo( $entry, $title );
if ( $row->page_is_redirect ) {
- $entry['redirect'] = '';
+ $entry['redirect'] = true;
}
if ( $lllang ) {
@@ -154,7 +154,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'll' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'll' );
} else {
$resultPageSet->populateFromTitles( $pages );
}
@@ -168,7 +168,9 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
return array(
'lang' => null,
'title' => null,
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -194,34 +196,12 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- return array(
- 'lang' => 'Language for the language link',
- 'title' => "Language link to search for. Must be used with {$this->getModulePrefix()}lang",
- 'continue' => 'When more results are available, use this to continue',
- 'prop' => array(
- 'Which properties to get',
- ' lllang - Adds the language code of the language link',
- ' lltitle - Adds the title of the language link',
- ),
- 'limit' => 'How many total pages to return',
- 'dir' => 'The direction in which to list',
- );
- }
-
- 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',
- 'all links to a title (with a given language).',
- 'Using neither parameter is effectively "All Language Links".',
- 'Note that this may not consider language links added by extensions.',
- );
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=langbacklinks&lbltitle=Test&lbllang=fr',
- 'api.php?action=query&generator=langbacklinks&glbltitle=Test&glbllang=fr&prop=info'
+ 'action=query&list=langbacklinks&lbltitle=Test&lbllang=fr'
+ => 'apihelp-query+langbacklinks-example-simple',
+ 'action=query&generator=langbacklinks&glbltitle=Test&glbllang=fr&prop=info'
+ => 'apihelp-query+langbacklinks-example-generator',
);
}
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index da05f273..5919ee97 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -124,7 +124,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
if ( isset( $prop['autonym'] ) ) {
$entry['autonym'] = Language::fetchLanguageName( $row->ll_lang );
}
- ApiResult::setContent( $entry, $row->ll_title );
+ ApiResult::setContentValue( $entry, 'title', $row->ll_title );
$fit = $this->addPageSubItem( $row->ll_from, $entry );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', "{$row->ll_from}|{$row->ll_lang}" );
@@ -140,18 +140,6 @@ class ApiQueryLangLinks extends ApiQueryBase {
public function getAllowedParams() {
global $wgContLang;
return array(
- '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,
- 'url' => array(
- ApiBase::PARAM_DFLT => false,
- ApiBase::PARAM_DEPRECATED => true,
- ),
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
@@ -170,36 +158,27 @@ class ApiQueryLangLinks extends ApiQueryBase {
)
),
'inlanguagecode' => $wgContLang->getCode(),
- );
- }
-
- public function getParamDescription() {
- 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 (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',
+ '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' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
+ 'url' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
),
- 'lang' => 'Language code',
- 'title' => "Link to search for. Must be used with {$this->getModulePrefix()}lang",
- 'dir' => 'The direction in which to list',
- 'inlanguagecode' => 'Language code for localised language names',
);
}
- public function getDescription() {
- return 'Returns all interlanguage links from the given page(s).';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&prop=langlinks&titles=Main%20Page&redirects='
- => 'Get interlanguage links from the [[Main Page]]',
+ 'action=query&prop=langlinks&titles=Main%20Page&redirects='
+ => 'apihelp-query+langlinks-example-simple',
);
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 71329c4d..3bd37144 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -34,26 +34,20 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
const LINKS = 'links';
const TEMPLATES = 'templates';
- private $table, $prefix, $description, $helpUrl;
+ private $table, $prefix, $helpUrl;
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->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#links_.2F_pl';
break;
case self::TEMPLATES:
$this->table = 'templatelinks';
$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->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#templates_.2F_tl';
break;
default:
@@ -197,7 +191,9 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
$this->titlesParam => array(
ApiBase::PARAM_ISMULTI => true,
),
@@ -211,32 +207,17 @@ 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",
- 'continue' => 'When more results are available, use this to continue',
- $this->titlesParam => $this->titlesParamDescription,
- 'dir' => 'The direction in which to list',
- );
- }
-
- public function getDescription() {
- return "Returns all {$this->description}s from the given page(s).";
- }
-
- public function getExamples() {
- $desc = $this->description;
+ protected function getExamplesMessages() {
$name = $this->getModuleName();
+ $path = $this->getModulePath();
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",
+ "action=query&prop={$name}&titles=Main%20Page"
+ => "apihelp-{$path}-example-simple",
+ "action=query&generator={$name}&titles=Main%20Page&prop=info"
+ => "apihelp-{$path}-example-generator",
+ "action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10"
+ => "apihelp-{$path}-example-namespaces",
);
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index d9dbb5e6..7b2381f4 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -230,19 +230,17 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
$vals = $this->extractRowInfo( $row );
- if ( !$vals ) {
- continue;
- }
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
break;
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'item' );
}
/**
+ * @deprecated since 1.25 Use LogFormatter::formatParametersForApi instead
* @param ApiResult $result
* @param array $vals
* @param string $params
@@ -255,100 +253,23 @@ class ApiQueryLogEvents extends ApiQueryBase {
public static function addLogParams( $result, &$vals, $params, $type,
$action, $ts, $legacy = false
) {
- switch ( $type ) {
- case 'move':
- if ( $legacy ) {
- $targetKey = 0;
- $noredirKey = 1;
- } else {
- $targetKey = '4::target';
- $noredirKey = '5::noredir';
- }
+ wfDeprecated( __METHOD__, '1.25' );
- if ( isset( $params[$targetKey] ) ) {
- $title = Title::newFromText( $params[$targetKey] );
- if ( $title ) {
- $vals2 = array();
- ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' );
- $vals[$type] = $vals2;
- }
- }
- if ( isset( $params[$noredirKey] ) && $params[$noredirKey] ) {
- $vals[$type]['suppressedredirect'] = '';
- }
- $params = null;
- break;
- case 'patrol':
- if ( $legacy ) {
- $cur = 0;
- $prev = 1;
- $auto = 2;
- } else {
- $cur = '4::curid';
- $prev = '5::previd';
- $auto = '6::auto';
- }
- $vals2 = array();
- $vals2['cur'] = $params[$cur];
- $vals2['prev'] = $params[$prev];
- $vals2['auto'] = $params[$auto];
- $vals[$type] = $vals2;
- $params = null;
- break;
- case 'rights':
- $vals2 = array();
- if ( $legacy ) {
- list( $vals2['old'], $vals2['new'] ) = $params;
- } else {
- $vals2['new'] = implode( ', ', $params['5::newgroups'] );
- $vals2['old'] = implode( ', ', $params['4::oldgroups'] );
- }
- $vals[$type] = $vals2;
- $params = null;
- break;
- case 'block':
- if ( $action == 'unblock' ) {
- break;
- }
- $vals2 = array();
- list( $vals2['duration'], $vals2['flags'] ) = $params;
-
- // Indefinite blocks have no expiry time
- if ( SpecialBlock::parseExpiryInput( $params[0] ) !== wfGetDB( DB_SLAVE )->getInfinity() ) {
- $vals2['expiry'] = wfTimestamp( TS_ISO_8601,
- strtotime( $params[0], wfTimestamp( TS_UNIX, $ts ) ) );
- }
- $vals[$type] = $vals2;
- $params = null;
- break;
- case 'upload':
- if ( isset( $params['img_timestamp'] ) ) {
- $params['img_timestamp'] = wfTimestamp( TS_ISO_8601, $params['img_timestamp'] );
- }
- break;
- }
- if ( !is_null( $params ) ) {
- $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;
- }
- $result->setIndexedTagName( $logParams, 'param' );
- $result->setIndexedTagName_recursive( $logParams, 'param' );
- $vals = array_merge( $vals, $logParams );
- }
+ $entry = new ManualLogEntry( $type, $action );
+ $entry->setParameters( $params );
+ $entry->setTimestamp( $ts );
+ $entry->setLegacy( $legacy );
+ $formatter = LogFormatter::newFromEntry( $entry );
+ $vals['params'] = $formatter->formatParametersForApi();
return $vals;
}
private function extractRowInfo( $row ) {
$logEntry = DatabaseLogEntry::newFromRow( $row );
- $vals = array();
+ $vals = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
$anyHidden = false;
$user = $this->getUser();
@@ -362,7 +283,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( $this->fld_title || $this->fld_ids || $this->fld_details && $row->log_params !== '' ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
- $vals['actionhidden'] = '';
+ $vals['actionhidden'] = true;
$anyHidden = true;
}
if ( LogEventsList::userCan( $row, LogPage::DELETED_ACTION, $user ) ) {
@@ -373,16 +294,8 @@ class ApiQueryLogEvents extends ApiQueryBase {
$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()
- );
+ if ( $this->fld_details ) {
+ $vals['params'] = LogFormatter::newFromEntry( $logEntry )->formatParametersForApi();
}
}
}
@@ -394,7 +307,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( $this->fld_user || $this->fld_userid ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
- $vals['userhidden'] = '';
+ $vals['userhidden'] = true;
$anyHidden = true;
}
if ( LogEventsList::userCan( $row, LogPage::DELETED_USER, $user ) ) {
@@ -406,7 +319,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
if ( !$row->log_user ) {
- $vals['anon'] = '';
+ $vals['anon'] = true;
}
}
}
@@ -416,7 +329,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->log_comment ) ) {
if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
- $vals['commenthidden'] = '';
+ $vals['commenthidden'] = true;
$anyHidden = true;
}
if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $user ) ) {
@@ -433,7 +346,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( $this->fld_tags ) {
if ( $row->ts_tags ) {
$tags = explode( ',', $row->ts_tags );
- $this->getResult()->setIndexedTagName( $tags, 'tag' );
+ ApiResult::setIndexedTagName( $tags, 'tag' );
$vals['tags'] = $tags;
} else {
$vals['tags'] = array();
@@ -441,7 +354,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
if ( $anyHidden && LogEventsList::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) {
- $vals['suppressed'] = '';
+ $vals['suppressed'] = true;
}
return $vals;
@@ -473,7 +386,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
public function getAllowedParams( $flags = 0 ) {
$config = $this->getConfig();
- return array(
+ $ret = array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'ids|title|type|user|timestamp|comment|details',
@@ -510,14 +423,15 @@ class ApiQueryLogEvents extends ApiQueryBase {
ApiBase::PARAM_TYPE => array(
'newer',
'older'
- )
+ ),
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
),
'user' => null,
'title' => null,
'namespace' => array(
ApiBase::PARAM_TYPE => 'namespace'
),
- 'prefix' => null,
+ 'prefix' => array(),
'tag' => null,
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -526,52 +440,22 @@ class ApiQueryLogEvents extends ApiQueryBase {
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',
- ' ids - Adds the ID of the log event',
- ' title - Adds the title of the page for the log event',
- ' type - Adds the type of log event',
- ' user - Adds the user responsible for the log event',
- ' userid - Adds the user ID who was responsible for the log event',
- ' timestamp - Adds the timestamp for the event',
- ' comment - Adds the comment of the event',
- ' parsedcomment - Adds the parsed comment of the event',
- ' details - Lists additional details about the event',
- ' tags - Lists tags for the event',
- ),
- 'type' => 'Filter log entries to only this 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"
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- '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',
- 'continue' => 'When more results are available, use this to continue',
);
- }
- public function getDescription() {
- return 'Get events from logs.';
+ if ( $config->get( 'MiserMode' ) ) {
+ $ret['prefix'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
+ }
+
+ return $ret;
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=logevents'
+ 'action=query&list=logevents'
+ => 'apihelp-query+logevents-example-simple',
);
}
diff --git a/includes/api/ApiQueryORM.php b/includes/api/ApiQueryORM.php
index 469b2972..dc10c91c 100644
--- a/includes/api/ApiQueryORM.php
+++ b/includes/api/ApiQueryORM.php
@@ -205,7 +205,7 @@ abstract class ApiQueryORM extends ApiQueryBase {
* @param array $serializedResults
*/
protected function setIndexedTagNames( array &$serializedResults ) {
- $this->getResult()->setIndexedTagName( $serializedResults, $this->getRowName() );
+ ApiResult::setIndexedTagName( $serializedResults, $this->getRowName() );
}
/**
@@ -233,15 +233,19 @@ abstract class ApiQueryORM extends ApiQueryBase {
ApiBase::PARAM_TYPE => $this->getTable()->getFieldNames(),
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_REQUIRED => true,
+ ApiBase::PARAM_HELP_MSG => 'api-orm-param-props',
),
'limit' => array(
ApiBase::PARAM_DFLT => 20,
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
- ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
+ ApiBase::PARAM_HELP_MSG => 'api-orm-param-limit',
+ ),
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- 'continue' => null,
);
return array_merge( $this->getTable()->getAPIParams(), $params );
@@ -249,6 +253,7 @@ abstract class ApiQueryORM extends ApiQueryBase {
/**
* @see ApiBase::getParamDescription()
+ * @deprecated since 1.25
* @return array
*/
public function getParamDescription() {
diff --git a/includes/api/ApiQueryPagePropNames.php b/includes/api/ApiQueryPagePropNames.php
index 8cd9c6cf..11a29ff9 100644
--- a/includes/api/ApiQueryPagePropNames.php
+++ b/includes/api/ApiQueryPagePropNames.php
@@ -78,12 +78,14 @@ class ApiQueryPagePropNames extends ApiQueryBase {
}
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'p' );
}
public function getAllowedParams() {
return array(
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_DFLT => 10,
@@ -94,20 +96,10 @@ class ApiQueryPagePropNames extends ApiQueryBase {
);
}
- public function getParamDescription() {
- return array(
- 'continue' => 'When more results are available, use this to continue',
- 'limit' => 'The maximum number of pages to return',
- );
- }
-
- public function getDescription() {
- return 'List all page prop names in use on the wiki.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=pagepropnames' => 'Get first 10 prop names',
+ 'action=query&list=pagepropnames'
+ => 'apihelp-query+pagepropnames-example-simple',
);
}
diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php
index e370c39f..dd19bf23 100644
--- a/includes/api/ApiQueryPageProps.php
+++ b/includes/api/ApiQueryPageProps.php
@@ -110,6 +110,7 @@ class ApiQueryPageProps extends ApiQueryBase {
* @return bool True if it fits in the result
*/
private function addPageProps( $result, $page, $props ) {
+ ApiResult::setArrayType( $props, 'assoc' );
$fit = $result->addValue( array( 'query', 'pages', $page ), 'pageprops', $props );
if ( !$fit ) {
@@ -125,28 +126,19 @@ class ApiQueryPageProps extends ApiQueryBase {
public function getAllowedParams() {
return array(
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
),
);
}
- 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',
- );
- }
-
- public function getDescription() {
- return 'Get various properties defined in the page content.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&prop=pageprops&titles=Category:Foo',
+ 'action=query&prop=pageprops&titles=Category:Foo'
+ => 'apihelp-query+pageprops-example-simple',
);
}
diff --git a/includes/api/ApiQueryPagesWithProp.php b/includes/api/ApiQueryPagesWithProp.php
index b6c85253..7bcaf247 100644
--- a/includes/api/ApiQueryPagesWithProp.php
+++ b/includes/api/ApiQueryPagesWithProp.php
@@ -99,7 +99,9 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
}
if ( $resultPageSet === null ) {
- $vals = array();
+ $vals = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
if ( $fld_ids ) {
$vals['pageid'] = (int)$row->page_id;
}
@@ -121,7 +123,7 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
}
if ( $resultPageSet === null ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
}
}
@@ -140,7 +142,9 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
'value',
)
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_TYPE => 'limit',
ApiBase::PARAM_DFLT => 10,
@@ -158,31 +162,12 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- return array(
- 'propname' => 'Page prop for which to enumerate pages',
- 'prop' => array(
- 'What pieces of information to include',
- ' ids - Adds the page ID',
- ' title - Adds the title and namespace ID of the page',
- ' value - Adds the value of the page prop',
- ),
- 'dir' => 'In which direction to sort',
- 'continue' => 'When more results are available, use this to continue',
- 'limit' => 'The maximum number of pages to return',
- );
- }
-
- public function getDescription() {
- return 'List all pages using a given page prop.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
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__',
+ 'action=query&list=pageswithprop&pwppropname=displaytitle&pwpprop=ids|title|value'
+ => 'apihelp-query+pageswithprop-example-simple',
+ 'action=query&generator=pageswithprop&gpwppropname=notoc&prop=info'
+ => 'apihelp-query+pageswithprop-example-generator',
);
}
diff --git a/includes/api/ApiQueryPrefixSearch.php b/includes/api/ApiQueryPrefixSearch.php
index 4abd7f0e..8eb644fc 100644
--- a/includes/api/ApiQueryPrefixSearch.php
+++ b/includes/api/ApiQueryPrefixSearch.php
@@ -43,15 +43,25 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
$search = $params['search'];
$limit = $params['limit'];
$namespaces = $params['namespace'];
+ $offset = $params['offset'];
$searcher = new TitlePrefixSearch;
- $titles = $searcher->searchWithVariants( $search, $limit, $namespaces );
+ $titles = $searcher->searchWithVariants( $search, $limit + 1, $namespaces, $offset );
if ( $resultPageSet ) {
+ if ( count( $titles ) > $limit ) {
+ $this->setContinueEnumParameter( 'offset', $offset + $params['limit'] );
+ array_pop( $titles );
+ }
$resultPageSet->populateFromTitles( $titles );
+ foreach ( $titles as $index => $title ) {
+ $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset + 1 ) );
+ }
} else {
$result = $this->getResult();
+ $count = 0;
foreach ( $titles as $title ) {
- if ( !$limit-- ) {
+ if ( ++$count > $limit ) {
+ $this->setContinueEnumParameter( 'offset', $offset + $params['limit'] );
break;
}
$vals = array(
@@ -59,16 +69,17 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
'title' => $title->getPrefixedText(),
);
if ( $title->isSpecialPage() ) {
- $vals['special'] = '';
+ $vals['special'] = true;
} else {
$vals['pageid'] = intval( $title->getArticleId() );
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
+ $this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
break;
}
}
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ), $this->getModulePrefix()
);
}
@@ -97,24 +108,17 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX => 100,
ApiBase::PARAM_MAX2 => 200,
),
+ 'offset' => array(
+ ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
);
}
- 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() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=prefixsearch&pssearch=meaning',
+ 'action=query&list=prefixsearch&pssearch=meaning'
+ => 'apihelp-query+prefixsearch-example-simple',
);
}
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index 4c88be7a..fb65e5e2 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -156,7 +156,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName() ),
$this->getModulePrefix()
);
@@ -196,7 +196,8 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
ApiBase::PARAM_TYPE => array(
'newer',
'older'
- )
+ ),
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
),
'start' => array(
ApiBase::PARAM_TYPE => 'timestamp'
@@ -217,39 +218,18 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
'level'
)
),
- 'continue' => null,
- );
- }
-
- public function getParamDescription() {
- return array(
- 'namespace' => 'Only list titles in these namespaces',
- 'start' => 'Start listing at this protection timestamp',
- 'end' => 'Stop listing at this protection timestamp',
- 'dir' => $this->getDirectionDescription( $this->getModulePrefix() ),
- 'limit' => 'How many total pages to return',
- 'prop' => array(
- 'Which properties to get',
- ' timestamp - Adds the timestamp of when protection was added',
- ' user - Adds the user that added the protection',
- ' userid - Adds the user id that added the protection',
- ' comment - Adds the comment for the protection',
- ' parsedcomment - Adds the parsed comment for the protection',
- ' expiry - Adds the timestamp of when the protection will be lifted',
- ' level - Adds the protection level',
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- 'level' => 'Only list titles with these protection levels',
- 'continue' => 'When more results are available, use this to continue',
);
}
- public function getDescription() {
- return 'List all titles protected from creation.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=protectedtitles',
+ 'action=query&list=protectedtitles'
+ => 'apihelp-query+protectedtitles-example-simple',
+ 'action=query&generator=protectedtitles&gptnamespace=0&prop=linkshere'
+ => 'apihelp-query+protectedtitles-example-generator',
);
}
diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php
index 5ddd9450..650ac8fe 100644
--- a/includes/api/ApiQueryQueryPage.php
+++ b/includes/api/ApiQueryQueryPage.php
@@ -68,9 +68,9 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
$r = array( 'name' => $params['page'] );
if ( $qp->isCached() ) {
if ( !$qp->isCacheable() ) {
- $r['disabled'] = '';
+ $r['disabled'] = true;
} else {
- $r['cached'] = '';
+ $r['cached'] = true;
$ts = $qp->getCachedTimestamp();
if ( $ts ) {
$r['cachedtimestamp'] = wfTimestamp( TS_ISO_8601, $ts );
@@ -119,7 +119,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
}
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal(
+ $result->addIndexedTagName(
array( 'query', $this->getModuleName(), 'results' ),
'page'
);
@@ -144,7 +144,10 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
ApiBase::PARAM_TYPE => array_keys( $this->qpMap ),
ApiBase::PARAM_REQUIRED => true
),
- 'offset' => 0,
+ 'offset' => array(
+ ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -155,21 +158,10 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
- return array(
- 'page' => 'The name of the special page. Note, this is case sensitive',
- 'offset' => 'When more results are available, use this to continue',
- 'limit' => 'Number of results to return',
- );
- }
-
- public function getDescription() {
- return 'Get a list provided by a QueryPage-based special page.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=querypage&qppage=Ancientpages'
+ 'action=query&list=querypage&qppage=Ancientpages'
+ => 'apihelp-query+querypage-example-ancientpages',
);
}
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index 530557e6..a2c28443 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -131,7 +131,7 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'page' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'page' );
}
}
@@ -165,30 +165,15 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
);
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
return array(
- 'namespace' => 'Return pages in these namespaces only',
- 'limit' => 'Limit how many random pages will be returned',
- 'redirect' => 'Load a random redirect instead of a random page'
+ 'action=query&list=random&rnnamespace=0&rnlimit=2'
+ => 'apihelp-query+random-example-simple',
+ 'action=query&generator=random&grnnamespace=0&grnlimit=2&prop=info'
+ => 'apihelp-query+random-example-generator',
);
}
- 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.'
- );
- }
-
- public function getExamples() {
- return 'api.php?action=query&list=random&rnnamespace=0&rnlimit=2';
- }
-
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Random';
}
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 6f0c5d34..f6a64785 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -56,15 +56,16 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
return $this->tokenFunctions;
}
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
return array();
}
$this->tokenFunctions = array(
'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' )
);
- wfRunHooks( 'APIQueryRecentChangesTokens', array( &$this->tokenFunctions ) );
+ Hooks::run( 'APIQueryRecentChangesTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
}
@@ -178,7 +179,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( !is_null( $params['type'] ) ) {
try {
$this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
ApiBase::dieDebug( __METHOD__, $e->getMessage() );
}
}
@@ -383,9 +384,6 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
$vals = $this->extractRowInfo( $row );
/* Add that row's data to our final output. */
- if ( !$vals ) {
- continue;
- }
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
@@ -398,7 +396,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( is_null( $resultPageSet ) ) {
/* Format the result */
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'rc' );
} else {
$resultPageSet->populateFromTitles( $titles );
}
@@ -427,7 +425,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Create a new entry in the result for the title. */
if ( $this->fld_title || $this->fld_ids ) {
if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
- $vals['actionhidden'] = '';
+ $vals['actionhidden'] = true;
$anyHidden = true;
}
if ( $type !== RC_LOG ||
@@ -451,7 +449,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Add user data and 'anon' flag, if user is anonymous. */
if ( $this->fld_user || $this->fld_userid ) {
if ( $row->rc_deleted & Revision::DELETED_USER ) {
- $vals['userhidden'] = '';
+ $vals['userhidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
@@ -464,22 +462,16 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
if ( !$row->rc_user ) {
- $vals['anon'] = '';
+ $vals['anon'] = true;
}
}
}
/* 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'] = '';
- }
+ $vals['bot'] = (bool)$row->rc_bot;
+ $vals['new'] = $row->rc_type == RC_NEW;
+ $vals['minor'] = (bool)$row->rc_minor;
}
/* Add sizes of each revision. (Only available on 1.10+) */
@@ -496,7 +488,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Add edit summary / log summary. */
if ( $this->fld_comment || $this->fld_parsedcomment ) {
if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
- $vals['commenthidden'] = '';
+ $vals['commenthidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
@@ -511,45 +503,32 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
if ( $this->fld_redirect ) {
- if ( $row->page_is_redirect ) {
- $vals['redirect'] = '';
- }
+ $vals['redirect'] = (bool)$row->page_is_redirect;
}
/* Add the patrolled flag */
- if ( $this->fld_patrolled && $row->rc_patrolled == 1 ) {
- $vals['patrolled'] = '';
- }
-
- if ( $this->fld_patrolled && ChangesList::isUnpatrolled( $row, $user ) ) {
- $vals['unpatrolled'] = '';
+ if ( $this->fld_patrolled ) {
+ $vals['patrolled'] = $row->rc_patrolled == 1;
+ $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
}
if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
- $vals['actionhidden'] = '';
+ $vals['actionhidden'] = true;
$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()
- );
+ $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
}
}
if ( $this->fld_tags ) {
if ( $row->ts_tags ) {
$tags = explode( ',', $row->ts_tags );
- $this->getResult()->setIndexedTagName( $tags, 'tag' );
+ ApiResult::setIndexedTagName( $tags, 'tag' );
$vals['tags'] = $tags;
} else {
$vals['tags'] = array();
@@ -558,7 +537,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
- $vals['sha1hidden'] = '';
+ $vals['sha1hidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->rev_deleted, Revision::DELETED_TEXT, $user ) ) {
@@ -584,7 +563,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
- $vals['suppressed'] = '';
+ $vals['suppressed'] = true;
}
return $vals;
@@ -625,7 +604,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
ApiBase::PARAM_TYPE => array(
'newer',
'older'
- )
+ ),
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
),
'namespace' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -687,6 +667,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'type' => array(
+ ApiBase::PARAM_DFLT => 'edit|new|log',
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
'edit',
@@ -696,57 +677,18 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
)
),
'toponly' => false,
- 'continue' => null,
- );
- }
-
- public function getParamDescription() {
- $p = $this->getModulePrefix();
-
- return array(
- 'start' => 'The timestamp to start enumerating from',
- 'end' => 'The timestamp to end enumerating',
- 'dir' => $this->getDirectionDescription( $p ),
- 'namespace' => 'Filter log entries to only this namespace(s)',
- 'user' => 'Only list changes by this user',
- 'excludeuser' => 'Don\'t list changes by this user',
- 'prop' => array(
- 'Include additional pieces of information',
- ' user - Adds the user responsible for the edit and tags if they are an IP',
- ' userid - Adds the user id responsible for the edit',
- ' comment - Adds the comment for the edit',
- ' parsedcomment - Adds the parsed comment for the edit',
- ' flags - Adds flags for the edit',
- ' timestamp - Adds timestamp of the edit',
- ' title - Adds the page title of the edit',
- ' 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 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',
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- 'token' => 'Which tokens to obtain for each change',
- 'show' => array(
- 'Show only items that meet this criteria.',
- "For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon"
- ),
- 'type' => 'Which types of changes to show',
- 'limit' => 'How many total changes to return',
- 'tag' => 'Only list changes tagged with this tag',
- 'toponly' => 'Only list changes which are the latest revision',
- 'continue' => 'When more results are available, use this to continue',
);
}
- public function getDescription() {
- return 'Enumerate recent changes.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=recentchanges'
+ 'action=query&list=recentchanges'
+ => 'apihelp-query+recentchanges-example-simple',
+ 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
+ => 'apihelp-query+recentchanges-example-generator',
);
}
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index da4ec195..552ca3b4 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -32,20 +32,14 @@
*
* @ingroup API
*/
-class ApiQueryRevisions extends ApiQueryBase {
+class ApiQueryRevisions extends ApiQueryRevisionsBase {
- private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
- $token, $parseContent, $contentFormat;
+ private $token = null;
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 $tokenFunctions;
/** @deprecated since 1.24 */
@@ -59,15 +53,16 @@ class ApiQueryRevisions extends ApiQueryBase {
return $this->tokenFunctions;
}
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
return array();
}
$this->tokenFunctions = array(
'rollback' => array( 'ApiQueryRevisions', 'getRollbackToken' )
);
- wfRunHooks( 'APIQueryRevisionsTokens', array( &$this->tokenFunctions ) );
+ Hooks::run( 'APIQueryRevisionsTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
}
@@ -89,7 +84,7 @@ class ApiQueryRevisions extends ApiQueryBase {
array( $title->getPrefixedText(), $rev->getUserText() ) );
}
- public function execute() {
+ protected function run( ApiPageSet $resultPageSet = null ) {
$params = $this->extractRequestParams( false );
// If any of those parameters are used, work in 'enumeration' mode.
@@ -107,6 +102,11 @@ class ApiQueryRevisions extends ApiQueryBase {
// Optimization -- nothing to do
if ( $revCount === 0 && $pageCount === 0 ) {
+ // Nothing to do
+ return;
+ }
+ if ( $revCount > 0 && count( $pageSet->getLiveRevisionIDs() ) === 0 ) {
+ // We're in revisions mode but all given revisions are deleted
return;
}
@@ -127,75 +127,32 @@ class ApiQueryRevisions extends ApiQueryBase {
);
}
- if ( !is_null( $params['difftotext'] ) ) {
- $this->difftotext = $params['difftotext'];
- } elseif ( !is_null( $params['diffto'] ) ) {
- if ( $params['diffto'] == 'cur' ) {
- $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'
- );
- }
- // Check whether the revision exists and is readable,
- // DifferenceEngine returns a rather ambiguous empty
- // string if that's not the case
- if ( $params['diffto'] != 0 ) {
- $difftoRev = Revision::newFromID( $params['diffto'] );
- if ( !$difftoRev ) {
- $this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
- }
- if ( !$difftoRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
- $this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
- $params['diffto'] = null;
- }
- }
- $this->diffto = $params['diffto'];
+ // In non-enum mode, rvlimit can't be directly used. Use the maximum
+ // allowed value.
+ if ( !$enumRevMode ) {
+ $this->setParsedLimit = false;
+ $params['limit'] = 'max';
}
$db = $this->getDB();
- $this->addTables( 'page' );
- $this->addFields( Revision::selectFields() );
- $this->addWhere( 'page_id = rev_page' );
-
- $prop = array_flip( $params['prop'] );
-
- // Optional fields
- $this->fld_ids = isset( $prop['ids'] );
- // $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed?
- $this->fld_flags = isset( $prop['flags'] );
- $this->fld_timestamp = isset( $prop['timestamp'] );
- $this->fld_comment = isset( $prop['comment'] );
- $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
- $this->fld_size = isset( $prop['size'] );
- $this->fld_sha1 = isset( $prop['sha1'] );
- $this->fld_contentmodel = isset( $prop['contentmodel'] );
- $this->fld_userid = isset( $prop['userid'] );
- $this->fld_user = isset( $prop['user'] );
- $this->token = $params['token'];
-
- if ( !empty( $params['contentformat'] ) ) {
- $this->contentFormat = $params['contentformat'];
- }
-
- $userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
- $botMax = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
- $limit = $params['limit'];
- if ( $limit == 'max' ) {
- $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
- }
+ $this->addTables( array( 'revision', 'page' ) );
+ $this->addJoinConds(
+ array( 'page' => array( 'INNER JOIN', array( 'page_id = rev_page' ) ) )
+ );
- if ( !is_null( $this->token ) || $pageCount > 0 ) {
- $this->addFields( Revision::selectPageFields() );
+ if ( $resultPageSet === null ) {
+ $this->parseParameters( $params );
+ $this->token = $params['token'];
+ $this->addFields( Revision::selectFields() );
+ if ( $this->token !== null || $pageCount > 0 ) {
+ $this->addFields( Revision::selectPageFields() );
+ }
+ } else {
+ $this->limit = $this->getParameter( 'limit' ) ?: 10;
+ $this->addFields( array( 'rev_id', 'rev_page' ) );
}
- if ( isset( $prop['tags'] ) ) {
- $this->fld_tags = true;
+ if ( $this->fld_tags ) {
$this->addTables( 'tag_summary' );
$this->addJoinConds(
array( 'tag_summary' => array( 'LEFT JOIN', array( 'rev_id=ts_rev_id' ) ) )
@@ -211,7 +168,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->addWhereFld( 'ct_tag', $params['tag'] );
}
- if ( isset( $prop['content'] ) || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
+ if ( $this->fetchContent ) {
// For each page we will request, the user must have read rights for that page
$user = $this->getUser();
/** @var $title Title */
@@ -224,28 +181,11 @@ class ApiQueryRevisions extends ApiQueryBase {
}
$this->addTables( 'text' );
- $this->addWhere( 'rev_text_id=old_id' );
+ $this->addJoinConds(
+ array( 'text' => array( 'INNER JOIN', array( 'rev_text_id=old_id' ) ) )
+ );
$this->addFields( 'old_id' );
$this->addFields( Revision::selectTextFields() );
-
- $this->fld_content = isset( $prop['content'] );
-
- $this->expandTemplates = $params['expandtemplates'];
- $this->generateXML = $params['generatexml'];
- $this->parseContent = $params['parse'];
- if ( $this->parseContent ) {
- // Must manually initialize unset limit
- if ( is_null( $limit ) ) {
- $limit = 1;
- }
- // We are only going to parse 1 revision per request
- $this->validateLimit( 'limit', $limit, 1, 1, 1 );
- }
- if ( isset( $params['section'] ) ) {
- $this->section = $params['section'];
- } else {
- $this->section = false;
- }
}
// add user name, if needed
@@ -255,9 +195,6 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->addFields( Revision::selectUserFields() );
}
- // Bug 24166 - API error when using rvprop=tags
- $this->addTables( 'revision' );
-
if ( $enumRevMode ) {
// This is mostly to prevent parameter errors (and optimize SQL?)
if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) ) {
@@ -300,12 +237,6 @@ class ApiQueryRevisions extends ApiQueryBase {
$params['start'], $params['end'], false );
}
- // must manually initialize unset limit
- if ( is_null( $limit ) ) {
- $limit = 10;
- }
- $this->validateLimit( 'limit', $limit, 1, $userMax, $botMax );
-
// There is only one ID, use it
$ids = array_keys( $pageSet->getGoodTitles() );
$this->addWhereFld( 'rev_page', reset( $ids ) );
@@ -330,11 +261,7 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
} elseif ( $revCount > 0 ) {
- $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $revs = $pageSet->getRevisionIDs();
- if ( self::truncateArray( $revs, $max ) ) {
- $this->setWarning( "Too many values supplied for parameter 'revids': the limit is $max" );
- }
+ $revs = $pageSet->getLiveRevisionIDs();
// Get all revision IDs
$this->addWhereFld( 'rev_id', array_keys( $revs ) );
@@ -343,19 +270,11 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->addWhere( 'rev_id >= ' . intval( $params['continue'] ) );
}
$this->addOption( 'ORDER BY', 'rev_id' );
-
- // assumption testing -- we should never get more then $revCount rows.
- $limit = $revCount;
} elseif ( $pageCount > 0 ) {
- $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
$titles = $pageSet->getGoodTitles();
- if ( self::truncateArray( $titles, $max ) ) {
- $this->setWarning( "Too many values supplied for parameter 'titles': the limit is $max" );
- }
// When working in multi-page non-enumeration mode,
// limit to the latest revision only
- $this->addWhere( 'page_id=rev_page' );
$this->addWhere( 'page_latest=rev_id' );
// Get all page IDs
@@ -378,31 +297,20 @@ class ApiQueryRevisions extends ApiQueryBase {
'rev_page',
'rev_id'
) );
-
- // assumption testing -- we should never get more then $pageCount rows.
- $limit = $pageCount;
} else {
ApiBase::dieDebug( __METHOD__, 'param validation?' );
}
- $this->addOption( 'LIMIT', $limit + 1 );
+ $this->addOption( 'LIMIT', $this->limit + 1 );
$count = 0;
+ $generated = array();
$res = $this->select( __METHOD__ );
foreach ( $res as $row ) {
- if ( ++$count > $limit ) {
+ if ( ++$count > $this->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
- }
- $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
- break;
- }
-
- $fit = $this->addPageSubItem( $row->rev_page, $this->extractRowInfo( $row ), 'rev' );
- if ( !$fit ) {
if ( $enumRevMode ) {
$this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
} elseif ( $revCount > 0 ) {
@@ -413,434 +321,124 @@ class ApiQueryRevisions extends ApiQueryBase {
}
break;
}
- }
- }
-
- 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() );
- // $vals['oldid'] = intval( $row->rev_text_id ); // todo: should this be exposed?
- if ( !is_null( $revision->getParentId() ) ) {
- $vals['parentid'] = intval( $revision->getParentId() );
- }
- }
-
- if ( $this->fld_flags && $revision->isMinor() ) {
- $vals['minor'] = '';
- }
-
- if ( $this->fld_user || $this->fld_userid ) {
- if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
- $vals['userhidden'] = '';
- $anyHidden = true;
- }
- if ( $revision->userCan( Revision::DELETED_USER, $user ) ) {
- if ( $this->fld_user ) {
- $vals['user'] = $revision->getRawUserText();
- }
- $userid = $revision->getRawUser();
- if ( !$userid ) {
- $vals['anon'] = '';
- }
-
- if ( $this->fld_userid ) {
- $vals['userid'] = $userid;
- }
- }
- }
-
- if ( $this->fld_timestamp ) {
- $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
- }
-
- if ( $this->fld_size ) {
- if ( !is_null( $revision->getSize() ) ) {
- $vals['size'] = intval( $revision->getSize() );
- } else {
- $vals['size'] = 0;
- }
- }
-
- 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'] = '';
- }
- }
- }
-
- if ( $this->fld_contentmodel ) {
- $vals['contentmodel'] = $revision->getContentModel();
- }
-
- if ( $this->fld_comment || $this->fld_parsedcomment ) {
- if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
- $vals['commenthidden'] = '';
- $anyHidden = true;
- }
- if ( $revision->userCan( Revision::DELETED_COMMENT, $user ) ) {
- $comment = $revision->getRawComment();
-
- if ( $this->fld_comment ) {
- $vals['comment'] = $comment;
- }
-
- if ( $this->fld_parsedcomment ) {
- $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
- }
- }
- }
- if ( $this->fld_tags ) {
- if ( $row->ts_tags ) {
- $tags = explode( ',', $row->ts_tags );
- $this->getResult()->setIndexedTagName( $tags, 'tag' );
- $vals['tags'] = $tags;
+ if ( $resultPageSet !== null ) {
+ $generated[] = $row->rev_id;
} else {
- $vals['tags'] = array();
- }
- }
-
- if ( !is_null( $this->token ) ) {
- $tokenFunctions = $this->getTokenFunctions();
- foreach ( $this->token as $t ) {
- $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
- if ( $val === false ) {
- $this->setWarning( "Action '$t' is not allowed for the current user" );
- } else {
- $vals[$t . 'token'] = $val;
- }
- }
- }
-
- $content = null;
- global $wgParser;
- if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
- $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'
- );
- }
- }
- if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
- $vals['texthidden'] = '';
- $anyHidden = true;
- } elseif ( !$content ) {
- $vals['textmissing'] = '';
- }
- }
- 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
- );
- $dom = $wgParser->preprocessToDom( $t );
- if ( is_callable( array( $dom, 'saveXML' ) ) ) {
- $xml = $dom->saveXML();
- } else {
- $xml = $dom->__toString();
+ $revision = new Revision( $row );
+ $rev = $this->extractRevisionInfo( $revision, $row );
+
+ if ( $this->token !== null ) {
+ $title = $revision->getTitle();
+ $tokenFunctions = $this->getTokenFunctions();
+ foreach ( $this->token as $t ) {
+ $val = call_user_func( $tokenFunctions[$t], $title->getArticleID(), $title, $revision );
+ if ( $val === false ) {
+ $this->setWarning( "Action '$t' is not allowed for the current user" );
+ } else {
+ $rev[$t . 'token'] = $val;
+ }
}
- $vals['parsetree'] = $xml;
- } else {
- $this->setWarning( "Conversion to XML is supported for wikitext only, " .
- $title->getPrefixedDBkey() .
- " uses content model " . $content->getModel() );
- }
- }
-
- if ( $this->expandTemplates && !$this->parseContent ) {
- #XXX: implement template expansion for all content types in ContentHandler?
- if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
- $text = $content->getNativeData();
-
- $text = $wgParser->preprocess(
- $text,
- $title,
- ParserOptions::newFromContext( $this->getContext() )
- );
- } else {
- $this->setWarning( "Template expansion is supported for wikitext only, " .
- $title->getPrefixedDBkey() .
- " uses content model " . $content->getModel() );
-
- $text = false;
}
- }
- if ( $this->parseContent ) {
- $po = $content->getParserOutput(
- $title,
- $revision->getId(),
- ParserOptions::newFromContext( $this->getContext() )
- );
- $text = $po->getText();
- }
-
- if ( $text === null ) {
- $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
- $model = $content->getModel();
-
- if ( !$content->isSupportedFormat( $format ) ) {
- $name = $title->getPrefixedDBkey();
-
- $this->dieUsage( "The requested format {$this->contentFormat} is not supported " .
- "for content model $model used by $name", 'badformat' );
- }
-
- $text = $content->serialize( $format );
- // always include format and model.
- // Format is needed to deserialize, model is needed to interpret.
- $vals['contentformat'] = $format;
- $vals['contentmodel'] = $model;
- }
-
- if ( $text !== false ) {
- ApiResult::setContent( $vals, $text );
- }
- }
-
- if ( $content && ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) ) {
- static $n = 0; // Number of uncached diffs we've had
-
- if ( $n < $this->getConfig()->get( 'APIMaxUncachedDiffs' ) ) {
- $vals['diff'] = array();
- $context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $title );
- $handler = $revision->getContentHandler();
-
- if ( !is_null( $this->difftotext ) ) {
- $model = $title->getContentModel();
-
- if ( $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' );
+ $fit = $this->addPageSubItem( $row->rev_page, $rev, 'rev' );
+ if ( !$fit ) {
+ if ( $enumRevMode ) {
+ $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
+ } elseif ( $revCount > 0 ) {
+ $this->setContinueEnumParameter( 'continue', intval( $row->rev_id ) );
+ } else {
+ $this->setContinueEnumParameter( 'continue', intval( $row->rev_page ) .
+ '|' . intval( $row->rev_id ) );
}
-
- $difftocontent = ContentHandler::makeContent(
- $this->difftotext,
- $title,
- $model,
- $this->contentFormat
- );
-
- $engine = $handler->createDifferenceEngine( $context );
- $engine->setContent( $content, $difftocontent );
- } else {
- $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
- $vals['diff']['from'] = $engine->getOldid();
- $vals['diff']['to'] = $engine->getNewid();
- }
- $difftext = $engine->getDiffBody();
- ApiResult::setContent( $vals['diff'], $difftext );
- if ( !$engine->wasCacheHit() ) {
- $n++;
+ break;
}
- } else {
- $vals['diff']['notcached'] = '';
}
}
- if ( $anyHidden && $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) {
- $vals['suppressed'] = '';
+ if ( $resultPageSet !== null ) {
+ $resultPageSet->populateFromRevisionIDs( $generated );
}
-
- return $vals;
}
public function getCacheMode( $params ) {
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';
+ return parent::getCacheMode( $params );
}
public function getAllowedParams() {
- return array(
- 'prop' => array(
- ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_DFLT => 'ids|timestamp|flags|comment|user',
- ApiBase::PARAM_TYPE => array(
- 'ids',
- 'flags',
- 'timestamp',
- 'user',
- 'userid',
- 'size',
- 'sha1',
- 'contentmodel',
- 'comment',
- 'parsedcomment',
- 'content',
- 'tags'
- )
- ),
- 'limit' => array(
- ApiBase::PARAM_TYPE => 'limit',
- ApiBase::PARAM_MIN => 1,
- ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
- ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
- ),
+ $ret = parent::getAllowedParams() + array(
'startid' => array(
- ApiBase::PARAM_TYPE => 'integer'
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'endid' => array(
- ApiBase::PARAM_TYPE => 'integer'
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'start' => array(
- ApiBase::PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'end' => array(
- ApiBase::PARAM_TYPE => 'timestamp'
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'dir' => array(
ApiBase::PARAM_DFLT => 'older',
ApiBase::PARAM_TYPE => array(
'newer',
'older'
- )
+ ),
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'user' => array(
- ApiBase::PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'excludeuser' => array(
- ApiBase::PARAM_TYPE => 'user'
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_HELP_MSG_INFO => array( array( 'singlepageonly' ) ),
),
'tag' => null,
- 'expandtemplates' => false,
- 'generatexml' => false,
- 'parse' => false,
- 'section' => null,
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),
- 'continue' => null,
- 'diffto' => null,
- 'difftotext' => null,
- 'contentformat' => array(
- ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
- ApiBase::PARAM_DFLT => null
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
);
- }
- public function getParamDescription() {
- $p = $this->getModulePrefix();
+ $ret['limit'][ApiBase::PARAM_HELP_MSG_INFO] = array( array( 'singlepageonly' ) );
- return array(
- 'prop' => array(
- 'Which properties to get for each revision:',
- ' ids - The ID of the revision',
- ' flags - Revision flags (minor)',
- ' timestamp - The timestamp of the revision',
- ' user - User that made the revision',
- ' userid - User id of revision creator',
- ' size - Length (bytes) of the revision',
- ' sha1 - SHA-1 (base 16) of the revision',
- ' contentmodel - Content model id',
- ' comment - Comment by the user for revision',
- ' parsedcomment - Parsed comment by the user for the revision',
- ' content - Text of the revision',
- ' tags - Tags for the revision',
- ),
- 'limit' => 'Limit how many revisions will be returned (enum)',
- 'startid' => 'From which revision id to start enumeration (enum)',
- 'endid' => 'Stop revision enumeration on this revid (enum)',
- 'start' => 'From which revision timestamp to start enumeration (enum)',
- 'end' => 'Enumerate up to this timestamp (enum)',
- 'dir' => $this->getDirectionDescription( $p, ' (enum)' ),
- 'user' => 'Only include revisions made by user (enum)',
- 'excludeuser' => 'Exclude revisions made by user (enum)',
- 'expandtemplates' => "Expand templates in revision content (requires {$p}prop=content)",
- 'generatexml' => "Generate XML parse tree for revision content (requires {$p}prop=content)",
- 'parse' => array( "Parse revision content (requires {$p}prop=content).",
- 'For performance reasons if this option is used, rvlimit is enforced to 1.' ),
- 'section' => 'Only retrieve the content of this section number',
- 'token' => 'Which tokens to obtain for each revision',
- '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',
- ),
- 'tag' => 'Only list revisions tagged with this tag',
- 'contentformat' => 'Serialization format used for difftotext and expected for output of content',
- );
- }
-
- public function getDescription() {
- return array(
- '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).'
- );
+ return $ret;
}
- public function getExamples() {
+ protected function getExamplesMessages() {
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',
- 'Get last 5 revisions of the "Main Page"',
- ' 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',
- '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',
- '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',
- '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',
+ 'action=query&prop=revisions&titles=API|Main%20Page&' .
+ 'rvprop=timestamp|user|comment|content'
+ => 'apihelp-query+revisions-example-content',
+ 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment'
+ => 'apihelp-query+revisions-example-last5',
+ 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvdir=newer'
+ => 'apihelp-query+revisions-example-first5',
+ 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvdir=newer&rvstart=2006-05-01T00:00:00Z'
+ => 'apihelp-query+revisions-example-first5-after',
+ 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1'
+ => 'apihelp-query+revisions-example-first5-not-localhost',
+ 'action=query&prop=revisions&titles=Main%20Page&rvlimit=5&' .
+ 'rvprop=timestamp|user|comment&rvuser=MediaWiki%20default'
+ => 'apihelp-query+revisions-example-first5-user',
);
}
diff --git a/includes/api/ApiQueryRevisionsBase.php b/includes/api/ApiQueryRevisionsBase.php
new file mode 100644
index 00000000..64f6120a
--- /dev/null
+++ b/includes/api/ApiQueryRevisionsBase.php
@@ -0,0 +1,477 @@
+<?php
+/**
+ *
+ *
+ * Created on Oct 3, 2014 as a split from ApiQueryRevisions
+ *
+ * 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
+ */
+
+/**
+ * A base class for functions common to producing a list of revisions.
+ *
+ * @ingroup API
+ */
+abstract class ApiQueryRevisionsBase extends ApiQueryGeneratorBase {
+
+ protected $limit, $diffto, $difftotext, $expandTemplates, $generateXML, $section,
+ $parseContent, $fetchContent, $contentFormat, $setParsedLimit = true;
+
+ protected $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;
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param ApiPageSet $resultPageSet
+ * @return void
+ */
+ abstract protected function run( ApiPageSet $resultPageSet = null );
+
+ /**
+ * Parse the parameters into the various instance fields.
+ *
+ * @param array $params
+ */
+ protected function parseParameters( $params ) {
+ if ( !is_null( $params['difftotext'] ) ) {
+ $this->difftotext = $params['difftotext'];
+ } elseif ( !is_null( $params['diffto'] ) ) {
+ if ( $params['diffto'] == 'cur' ) {
+ $params['diffto'] = 0;
+ }
+ if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
+ && $params['diffto'] != 'prev' && $params['diffto'] != 'next'
+ ) {
+ $p = $this->getModulePrefix();
+ $this->dieUsage(
+ "{$p}diffto 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
+ // string if that's not the case
+ if ( $params['diffto'] != 0 ) {
+ $difftoRev = Revision::newFromId( $params['diffto'] );
+ if ( !$difftoRev ) {
+ $this->dieUsageMsg( array( 'nosuchrevid', $params['diffto'] ) );
+ }
+ if ( !$difftoRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $this->setWarning( "Couldn't diff to r{$difftoRev->getID()}: content is hidden" );
+ $params['diffto'] = null;
+ }
+ }
+ $this->diffto = $params['diffto'];
+ }
+
+ $prop = array_flip( $params['prop'] );
+
+ $this->fld_ids = isset( $prop['ids'] );
+ $this->fld_flags = isset( $prop['flags'] );
+ $this->fld_timestamp = isset( $prop['timestamp'] );
+ $this->fld_comment = isset( $prop['comment'] );
+ $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
+ $this->fld_size = isset( $prop['size'] );
+ $this->fld_sha1 = isset( $prop['sha1'] );
+ $this->fld_content = isset( $prop['content'] );
+ $this->fld_contentmodel = isset( $prop['contentmodel'] );
+ $this->fld_userid = isset( $prop['userid'] );
+ $this->fld_user = isset( $prop['user'] );
+ $this->fld_tags = isset( $prop['tags'] );
+
+ if ( !empty( $params['contentformat'] ) ) {
+ $this->contentFormat = $params['contentformat'];
+ }
+
+ $this->limit = $params['limit'];
+
+ $this->fetchContent = $this->fld_content || !is_null( $this->diffto )
+ || !is_null( $this->difftotext );
+
+ $smallLimit = false;
+ if ( $this->fetchContent ) {
+ $smallLimit = true;
+ $this->expandTemplates = $params['expandtemplates'];
+ $this->generateXML = $params['generatexml'];
+ $this->parseContent = $params['parse'];
+ if ( $this->parseContent ) {
+ // Must manually initialize unset limit
+ if ( is_null( $this->limit ) ) {
+ $this->limit = 1;
+ }
+ }
+ if ( isset( $params['section'] ) ) {
+ $this->section = $params['section'];
+ } else {
+ $this->section = false;
+ }
+ }
+
+ $userMax = $this->parseContent ? 1 : ( $smallLimit ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
+ $botMax = $this->parseContent ? 1 : ( $smallLimit ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
+ if ( $this->limit == 'max' ) {
+ $this->limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
+ if ( $this->setParsedLimit ) {
+ $this->getResult()->addParsedLimit( $this->getModuleName(), $this->limit );
+ }
+ }
+
+ if ( is_null( $this->limit ) ) {
+ $this->limit = 10;
+ }
+ $this->validateLimit( 'limit', $this->limit, 1, $userMax, $botMax );
+ }
+
+ /**
+ * Extract information from the Revision
+ *
+ * @param Revision $revision
+ * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set
+ * @return array
+ */
+ protected function extractRevisionInfo( Revision $revision, $row ) {
+ $title = $revision->getTitle();
+ $user = $this->getUser();
+ $vals = array();
+ $anyHidden = false;
+
+ if ( $this->fld_ids ) {
+ $vals['revid'] = intval( $revision->getId() );
+ if ( !is_null( $revision->getParentId() ) ) {
+ $vals['parentid'] = intval( $revision->getParentId() );
+ }
+ }
+
+ if ( $this->fld_flags ) {
+ $vals['minor'] = $revision->isMinor();
+ }
+
+ if ( $this->fld_user || $this->fld_userid ) {
+ if ( $revision->isDeleted( Revision::DELETED_USER ) ) {
+ $vals['userhidden'] = true;
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_USER, $user ) ) {
+ if ( $this->fld_user ) {
+ $vals['user'] = $revision->getUserText( Revision::RAW );
+ }
+ $userid = $revision->getUser( Revision::RAW );
+ if ( !$userid ) {
+ $vals['anon'] = true;
+ }
+
+ if ( $this->fld_userid ) {
+ $vals['userid'] = $userid;
+ }
+ }
+ }
+
+ if ( $this->fld_timestamp ) {
+ $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
+ }
+
+ if ( $this->fld_size ) {
+ if ( !is_null( $revision->getSize() ) ) {
+ $vals['size'] = intval( $revision->getSize() );
+ } else {
+ $vals['size'] = 0;
+ }
+ }
+
+ if ( $this->fld_sha1 ) {
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['sha1hidden'] = true;
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_TEXT, $user ) ) {
+ if ( $revision->getSha1() != '' ) {
+ $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
+ }
+ }
+
+ if ( $this->fld_contentmodel ) {
+ $vals['contentmodel'] = $revision->getContentModel();
+ }
+
+ if ( $this->fld_comment || $this->fld_parsedcomment ) {
+ if ( $revision->isDeleted( Revision::DELETED_COMMENT ) ) {
+ $vals['commenthidden'] = true;
+ $anyHidden = true;
+ }
+ if ( $revision->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $comment = $revision->getComment( Revision::RAW );
+
+ if ( $this->fld_comment ) {
+ $vals['comment'] = $comment;
+ }
+
+ if ( $this->fld_parsedcomment ) {
+ $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
+ }
+ }
+ }
+
+ if ( $this->fld_tags ) {
+ if ( $row->ts_tags ) {
+ $tags = explode( ',', $row->ts_tags );
+ ApiResult::setIndexedTagName( $tags, 'tag' );
+ $vals['tags'] = $tags;
+ } else {
+ $vals['tags'] = array();
+ }
+ }
+
+ $content = null;
+ global $wgParser;
+ if ( $this->fetchContent ) {
+ $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'
+ );
+ }
+ }
+ if ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $vals['texthidden'] = true;
+ $anyHidden = true;
+ } elseif ( !$content ) {
+ $vals['textmissing'] = true;
+ }
+ }
+ 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() ),
+ Parser::OT_PREPROCESS
+ );
+ $dom = $wgParser->preprocessToDom( $t );
+ if ( is_callable( array( $dom, 'saveXML' ) ) ) {
+ $xml = $dom->saveXML();
+ } else {
+ $xml = $dom->__toString();
+ }
+ $vals['parsetree'] = $xml;
+ } else {
+ $vals['badcontentformatforparsetree'] = true;
+ $this->setWarning( "Conversion to XML is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() );
+ }
+ }
+
+ if ( $this->expandTemplates && !$this->parseContent ) {
+ #XXX: implement template expansion for all content types in ContentHandler?
+ if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
+ $text = $content->getNativeData();
+
+ $text = $wgParser->preprocess(
+ $text,
+ $title,
+ ParserOptions::newFromContext( $this->getContext() )
+ );
+ } else {
+ $this->setWarning( "Template expansion is supported for wikitext only, " .
+ $title->getPrefixedDBkey() .
+ " uses content model " . $content->getModel() );
+ $vals['badcontentformat'] = true;
+ $text = false;
+ }
+ }
+ if ( $this->parseContent ) {
+ $po = $content->getParserOutput(
+ $title,
+ $revision->getId(),
+ ParserOptions::newFromContext( $this->getContext() )
+ );
+ $text = $po->getText();
+ }
+
+ if ( $text === null ) {
+ $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
+ $model = $content->getModel();
+
+ if ( !$content->isSupportedFormat( $format ) ) {
+ $name = $title->getPrefixedDBkey();
+ $this->setWarning( "The requested format {$this->contentFormat} is not " .
+ "supported for content model $model used by $name" );
+ $vals['badcontentformat'] = true;
+ $text = false;
+ } else {
+ $text = $content->serialize( $format );
+ // always include format and model.
+ // Format is needed to deserialize, model is needed to interpret.
+ $vals['contentformat'] = $format;
+ $vals['contentmodel'] = $model;
+ }
+ }
+
+ if ( $text !== false ) {
+ ApiResult::setContentValue( $vals, 'content', $text );
+ }
+ }
+
+ if ( $content && ( !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) ) {
+ static $n = 0; // Number of uncached diffs we've had
+
+ if ( $n < $this->getConfig()->get( 'APIMaxUncachedDiffs' ) ) {
+ $vals['diff'] = array();
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $title );
+ $handler = $revision->getContentHandler();
+
+ if ( !is_null( $this->difftotext ) ) {
+ $model = $title->getContentModel();
+
+ if ( $this->contentFormat
+ && !ContentHandler::getForModelID( $model )->isSupportedFormat( $this->contentFormat )
+ ) {
+ $name = $title->getPrefixedDBkey();
+ $this->setWarning( "The requested format {$this->contentFormat} is not " .
+ "supported for content model $model used by $name" );
+ $vals['diff']['badcontentformat'] = true;
+ $engine = null;
+ } else {
+ $difftocontent = ContentHandler::makeContent(
+ $this->difftotext,
+ $title,
+ $model,
+ $this->contentFormat
+ );
+
+ $engine = $handler->createDifferenceEngine( $context );
+ $engine->setContent( $content, $difftocontent );
+ }
+ } else {
+ $engine = $handler->createDifferenceEngine( $context, $revision->getID(), $this->diffto );
+ $vals['diff']['from'] = $engine->getOldid();
+ $vals['diff']['to'] = $engine->getNewid();
+ }
+ if ( $engine ) {
+ $difftext = $engine->getDiffBody();
+ ApiResult::setContentValue( $vals['diff'], 'body', $difftext );
+ if ( !$engine->wasCacheHit() ) {
+ $n++;
+ }
+ }
+ } else {
+ $vals['diff']['notcached'] = true;
+ }
+ }
+
+ if ( $anyHidden && $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+ $vals['suppressed'] = true;
+ }
+
+ return $vals;
+ }
+
+ public function getCacheMode( $params ) {
+ if ( $this->userCanSeeRevDel() ) {
+ return 'private';
+ }
+
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DFLT => 'ids|timestamp|flags|comment|user',
+ ApiBase::PARAM_TYPE => array(
+ 'ids',
+ 'flags',
+ 'timestamp',
+ 'user',
+ 'userid',
+ 'size',
+ 'sha1',
+ 'contentmodel',
+ 'comment',
+ 'parsedcomment',
+ 'content',
+ 'tags'
+ ),
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-prop',
+ ),
+ 'limit' => array(
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-limit',
+ ),
+ 'expandtemplates' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-expandtemplates',
+ ),
+ 'generatexml' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-generatexml',
+ ),
+ 'parse' => array(
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-parse',
+ ),
+ 'section' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-section',
+ ),
+ 'diffto' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-diffto',
+ ),
+ 'difftotext' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotext',
+ ),
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-contentformat',
+ ),
+ );
+ }
+
+}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index b7dcd0ed..e29ef8d2 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -116,19 +116,21 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$this->dieUsage( $matches->getWikiText(), 'search-error' );
}
- $apiResult = $this->getResult();
- // Add search meta data to result
- if ( isset( $searchInfo['totalhits'] ) ) {
- $totalhits = $matches->getTotalHits();
- if ( $totalhits !== null ) {
+ if ( $resultPageSet === null ) {
+ $apiResult = $this->getResult();
+ // Add search meta data to result
+ if ( isset( $searchInfo['totalhits'] ) ) {
+ $totalhits = $matches->getTotalHits();
+ if ( $totalhits !== null ) {
+ $apiResult->addValue( array( 'query', 'searchinfo' ),
+ 'totalhits', $totalhits );
+ }
+ }
+ if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) {
$apiResult->addValue( array( 'query', 'searchinfo' ),
- 'totalhits', $totalhits );
+ 'suggestion', $matches->getSuggestionQuery() );
}
}
- if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) {
- $apiResult->addValue( array( 'query', 'searchinfo' ),
- 'suggestion', $matches->getSuggestionQuery() );
- }
// Add the search results to the result
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
@@ -151,7 +153,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
$title = $result->getTitle();
- if ( is_null( $resultPageSet ) ) {
+ if ( $resultPageSet === null ) {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
@@ -168,14 +170,14 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() );
}
if ( isset( $prop['titlesnippet'] ) ) {
- $vals['titlesnippet'] = $result->getTitleSnippet( $terms );
+ $vals['titlesnippet'] = $result->getTitleSnippet();
}
if ( !is_null( $result->getRedirectTitle() ) ) {
if ( isset( $prop['redirecttitle'] ) ) {
- $vals['redirecttitle'] = $result->getRedirectTitle();
+ $vals['redirecttitle'] = $result->getRedirectTitle()->getPrefixedText();
}
if ( isset( $prop['redirectsnippet'] ) ) {
- $vals['redirectsnippet'] = $result->getRedirectSnippet( $terms );
+ $vals['redirectsnippet'] = $result->getRedirectSnippet();
}
}
if ( !is_null( $result->getSectionTitle() ) ) {
@@ -202,56 +204,68 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
$hasInterwikiResults = false;
+ $totalhits = null;
if ( $interwiki && $resultPageSet === null && $matches->hasInterwikiResults() ) {
- $matches = $matches->getInterwikiResults();
- $hasInterwikiResults = true;
+ foreach ( $matches->getInterwikiResults() as $matches ) {
+ $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 );
+ // Include number of results if requested
+ if ( $resultPageSet === null && isset( $searchInfo['totalhits'] ) ) {
+ $totalhits += $matches->getTotalHits();
}
- }
- $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
- );
+ $result = $matches->next();
+ while ( $result ) {
+ $title = $result->getTitle();
+
+ if ( $resultPageSet === null ) {
+ $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;
+ }
+ } else {
+ $titles[] = $title;
+ }
- if ( !$fit ) {
- // We hit the limit. We can't really provide any meaningful
- // pagination info so just bail out
- break;
+ $result = $matches->next();
}
-
- $result = $matches->next();
+ }
+ if ( $totalhits !== null ) {
+ $apiResult->addValue( array( 'query', 'interwikisearchinfo' ),
+ 'totalhits', $totalhits );
}
}
- if ( is_null( $resultPageSet ) ) {
- $apiResult->setIndexedTagName_internal( array(
+ if ( $resultPageSet === null ) {
+ $apiResult->addIndexedTagName( array(
'query', $this->getModuleName()
), 'p' );
if ( $hasInterwikiResults ) {
- $apiResult->setIndexedTagName_internal( array(
+ $apiResult->addIndexedTagName( array(
'query', 'interwiki' . $this->getModuleName()
), 'p' );
}
} else {
$resultPageSet->populateFromTitles( $titles );
+ $offset = $params['offset'] + 1;
+ foreach ( $titles as $index => $title ) {
+ $resultPageSet->setGeneratorData( $title, array( 'index' => $index + $offset ) );
+ }
}
}
@@ -303,7 +317,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
),
ApiBase::PARAM_ISMULTI => true,
),
- 'offset' => 0,
+ 'offset' => array(
+ ApiBase::PARAM_DFLT => 0,
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -328,47 +345,14 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
return $params;
}
- public function getParamDescription() {
- $descriptions = array(
- 'search' => 'Search for all page titles (or content) that has this value',
- 'namespace' => 'The namespace(s) to enumerate',
- 'what' => 'Search inside the text or titles',
- 'info' => 'What metadata to return',
- 'prop' => array(
- 'What properties to return',
- ' 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 - 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 - DEPRECATED and IGNORED',
- ),
- 'offset' => 'Use this value to continue paging (return by query)',
- 'limit' => 'How many total pages to return',
- 'interwiki' => 'Include interwiki results in the search, if available'
- );
-
- if ( count( SearchEngine::getSearchTypes() ) > 1 ) {
- $descriptions['backend'] = 'Which search backend to use, if not the default';
- }
-
- return $descriptions;
- }
-
- public function getDescription() {
- return 'Perform a full text search.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=search&srsearch=meaning',
- 'api.php?action=query&list=search&srwhat=text&srsearch=meaning',
- 'api.php?action=query&generator=search&gsrsearch=meaning&prop=info',
+ 'action=query&list=search&srsearch=meaning'
+ => 'apihelp-query+search-example-simple',
+ 'action=query&list=search&srwhat=text&srsearch=meaning'
+ => 'apihelp-query+search-example-text',
+ 'action=query&generator=search&gsrsearch=meaning&prop=info'
+ => 'apihelp-query+search-example-generator',
);
}
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index 311438fd..b81e993b 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -69,6 +69,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'usergroups':
$fit = $this->appendUserGroups( $p, $params['numberingroup'] );
break;
+ case 'libraries':
+ $fit = $this->appendInstalledLibraries( $p );
+ break;
case 'extensions':
$fit = $this->appendExtensions( $p );
break;
@@ -147,24 +150,17 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$allowFrom = array( '' );
$allowException = true;
if ( !$config->get( 'AllowExternalImages' ) ) {
- if ( $config->get( 'EnableImageWhitelist' ) ) {
- $data['imagewhitelistenabled'] = '';
- }
+ $data['imagewhitelistenabled'] = (bool)$config->get( 'EnableImageWhitelist' );
$allowFrom = $config->get( 'AllowExternalImagesFrom' );
$allowException = !empty( $allowFrom );
}
if ( $allowException ) {
$data['externalimages'] = (array)$allowFrom;
- $this->getResult()->setIndexedTagName( $data['externalimages'], 'prefix' );
- }
-
- if ( !$config->get( 'DisableLangConversion' ) ) {
- $data['langconversion'] = '';
+ ApiResult::setIndexedTagName( $data['externalimages'], 'prefix' );
}
- if ( !$config->get( 'DisableTitleConversion' ) ) {
- $data['titleconversion'] = '';
- }
+ $data['langconversion'] = !$config->get( 'DisableLangConversion' );
+ $data['titleconversion'] = !$config->get( 'DisableTitleConversion' );
if ( $wgContLang->linkPrefixExtension() ) {
$linkPrefixCharset = $wgContLang->linkPrefixCharset();
@@ -177,11 +173,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$linktrail = $wgContLang->linkTrail();
- if ( $linktrail ) {
- $data['linktrail'] = $linktrail;
- } else {
- $data['linktrail'] = '';
- }
+ $data['linktrail'] = $linktrail ?: '';
+
+ $data['legaltitlechars'] = Title::legalChars();
global $IP;
$git = SpecialVersion::getGitHeadSha1( $IP );
@@ -205,7 +199,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$fallbacks[] = array( 'code' => $code );
}
$data['fallback'] = $fallbacks;
- $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
+ ApiResult::setIndexedTagName( $data['fallback'], 'lang' );
if ( $wgContLang->hasVariants() ) {
$variants = array();
@@ -216,21 +210,17 @@ class ApiQuerySiteinfo extends ApiQueryBase {
);
}
$data['variants'] = $variants;
- $this->getResult()->setIndexedTagName( $data['variants'], 'lang' );
+ ApiResult::setIndexedTagName( $data['variants'], 'lang' );
}
- if ( $wgContLang->isRTL() ) {
- $data['rtl'] = '';
- }
+ $data['rtl'] = $wgContLang->isRTL();
$data['fallback8bitEncoding'] = $wgContLang->fallback8bitEncoding();
- if ( wfReadOnly() ) {
- $data['readonly'] = '';
+ $data['readonly'] = wfReadOnly();
+ if ( $data['readonly'] ) {
$data['readonlyreason'] = wfReadOnlyReason();
}
- if ( $config->get( 'EnableWriteAPI' ) ) {
- $data['writeapi'] = '';
- }
+ $data['writeapi'] = (bool)$config->get( 'EnableWriteAPI' );
$tz = $config->get( 'Localtimezone' );
$offset = $config->get( 'LocalTZoffset' );
@@ -246,21 +236,22 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['scriptpath'] = $config->get( 'ScriptPath' );
$data['script'] = $config->get( 'Script' );
$data['variantarticlepath'] = $config->get( 'VariantArticlePath' );
+ $data[ApiResult::META_BC_BOOLS][] = 'variantarticlepath';
$data['server'] = $config->get( 'Server' );
$data['servername'] = $config->get( 'ServerName' );
$data['wikiid'] = wfWikiID();
$data['time'] = wfTimestamp( TS_ISO_8601, time() );
- if ( $config->get( 'MiserMode' ) ) {
- $data['misermode'] = '';
- }
+ $data['misermode'] = (bool)$config->get( 'MiserMode' );
$data['maxuploadsize'] = UploadBase::getMaxUploadSize();
$data['thumblimits'] = $config->get( 'ThumbLimits' );
- $this->getResult()->setIndexedTagName( $data['thumblimits'], 'limit' );
+ ApiResult::setArrayType( $data['thumblimits'], 'BCassoc' );
+ ApiResult::setIndexedTagName( $data['thumblimits'], 'limit' );
$data['imagelimits'] = array();
- $this->getResult()->setIndexedTagName( $data['imagelimits'], 'limit' );
+ ApiResult::setArrayType( $data['imagelimits'], 'BCassoc' );
+ ApiResult::setIndexedTagName( $data['imagelimits'], 'limit' );
foreach ( $config->get( 'ImageLimits' ) as $k => $limit ) {
$data['imagelimits'][$k] = array( 'width' => $limit[0], 'height' => $limit[1] );
}
@@ -272,37 +263,32 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['favicon'] = wfExpandUrl( $favicon, PROTO_RELATIVE );
}
- wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
+ Hooks::run( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendNamespaces( $property ) {
global $wgContLang;
- $data = array();
+ $data = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
$data[$ns] = array(
'id' => intval( $ns ),
'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
);
- ApiResult::setContent( $data[$ns], $title );
+ ApiResult::setContentValue( $data[$ns], 'name', $title );
$canonical = MWNamespace::getCanonicalName( $ns );
- if ( MWNamespace::hasSubpages( $ns ) ) {
- $data[$ns]['subpages'] = '';
- }
+ $data[$ns]['subpages'] = MWNamespace::hasSubpages( $ns );
if ( $canonical ) {
$data[$ns]['canonical'] = strtr( $canonical, '_', ' ' );
}
- if ( MWNamespace::isContent( $ns ) ) {
- $data[$ns]['content'] = '';
- }
-
- if ( MWNamespace::isNonincludable( $ns ) ) {
- $data[$ns]['nonincludable'] = '';
- }
+ $data[$ns]['content'] = MWNamespace::isContent( $ns );
+ $data[$ns]['nonincludable'] = MWNamespace::isNonincludable( $ns );
$contentmodel = MWNamespace::getNamespaceContentModel( $ns );
if ( $contentmodel ) {
@@ -310,7 +296,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
}
- $this->getResult()->setIndexedTagName( $data, 'ns' );
+ ApiResult::setIndexedTagName( $data, 'ns' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -329,13 +315,13 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$item = array(
'id' => intval( $ns )
);
- ApiResult::setContent( $item, strtr( $title, '_', ' ' ) );
+ ApiResult::setContentValue( $item, 'alias', strtr( $title, '_', ' ' ) );
$data[] = $item;
}
sort( $data );
- $this->getResult()->setIndexedTagName( $data, 'ns' );
+ ApiResult::setIndexedTagName( $data, 'ns' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -347,11 +333,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
foreach ( SpecialPageFactory::getNames() as $specialpage ) {
if ( isset( $aliases[$specialpage] ) ) {
$arr = array( 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] );
- $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
+ ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
$data[] = $arr;
}
}
- $this->getResult()->setIndexedTagName( $data, 'specialpage' );
+ ApiResult::setIndexedTagName( $data, 'specialpage' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -362,13 +348,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
foreach ( $wgContLang->getMagicWords() as $magicword => $aliases ) {
$caseSensitive = array_shift( $aliases );
$arr = array( 'name' => $magicword, 'aliases' => $aliases );
- if ( $caseSensitive ) {
- $arr['case-sensitive'] = '';
- }
- $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
+ $arr['case-sensitive'] = (bool)$caseSensitive;
+ ApiResult::setIndexedTagName( $arr['aliases'], 'alias' );
$data[] = $arr;
}
- $this->getResult()->setIndexedTagName( $data, 'magicword' );
+ ApiResult::setIndexedTagName( $data, 'magicword' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -397,20 +381,20 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$val = array();
$val['prefix'] = $prefix;
if ( isset( $row['iw_local'] ) && $row['iw_local'] == '1' ) {
- $val['local'] = '';
+ $val['local'] = true;
}
if ( isset( $row['iw_trans'] ) && $row['iw_trans'] == '1' ) {
- $val['trans'] = '';
+ $val['trans'] = true;
}
if ( isset( $langNames[$prefix] ) ) {
$val['language'] = $langNames[$prefix];
}
if ( in_array( $prefix, $localInterwikis ) ) {
- $val['localinterwiki'] = '';
+ $val['localinterwiki'] = true;
}
if ( in_array( $prefix, $extraLangPrefixes ) ) {
- $val['extralanglink'] = '';
+ $val['extralanglink'] = true;
$linktext = wfMessage( "interlanguage-link-$prefix" );
if ( !$linktext->isDisabled() ) {
@@ -424,20 +408,18 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT );
- if ( substr( $row['iw_url'], 0, 2 ) == '//' ) {
- $val['protorel'] = '';
- }
- if ( isset( $row['iw_wikiid'] ) ) {
+ $val['protorel'] = substr( $row['iw_url'], 0, 2 ) == '//';
+ if ( isset( $row['iw_wikiid'] ) && $row['iw_wikiid'] !== '' ) {
$val['wikiid'] = $row['iw_wikiid'];
}
- if ( isset( $row['iw_api'] ) ) {
+ if ( isset( $row['iw_api'] ) && $row['iw_api'] !== '' ) {
$val['api'] = $row['iw_api'];
}
$data[] = $val;
}
- $this->getResult()->setIndexedTagName( $data, 'iw' );
+ ApiResult::setIndexedTagName( $data, 'iw' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -472,7 +454,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$result = $this->getResult();
- $result->setIndexedTagName( $data, 'db' );
+ ApiResult::setIndexedTagName( $data, 'db' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -481,9 +463,6 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data = array();
$data['pages'] = intval( SiteStats::pages() );
$data['articles'] = intval( SiteStats::articles() );
- if ( !$this->getConfig()->get( 'DisableCounters' ) ) {
- $data['views'] = intval( SiteStats::views() );
- }
$data['edits'] = intval( SiteStats::edits() );
$data['images'] = intval( SiteStats::images() );
$data['users'] = intval( SiteStats::users() );
@@ -491,7 +470,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) );
$data['jobs'] = intval( SiteStats::jobs() );
- wfRunHooks( 'APIQuerySiteInfoStatisticsInfo', array( &$data ) );
+ Hooks::run( 'APIQuerySiteInfoStatisticsInfo', array( &$data ) );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -531,16 +510,16 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$groups = array_intersect( $rights[$group], $allGroups );
if ( $groups ) {
$arr[$type] = $groups;
- $result->setIndexedTagName( $arr[$type], 'group' );
+ ApiResult::setIndexedTagName( $arr[$type], 'group' );
}
}
}
- $result->setIndexedTagName( $arr['rights'], 'permission' );
+ ApiResult::setIndexedTagName( $arr['rights'], 'permission' );
$data[] = $arr;
}
- $result->setIndexedTagName( $data, 'group' );
+ ApiResult::setIndexedTagName( $data, 'group' );
return $result->addValue( 'query', $property, $data );
}
@@ -550,9 +529,39 @@ class ApiQuerySiteinfo extends ApiQueryBase {
foreach ( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) as $ext ) {
$data[] = array( 'ext' => $ext );
}
- $this->getResult()->setIndexedTagName( $data, 'fe' );
+ ApiResult::setIndexedTagName( $data, 'fe' );
+
+ return $this->getResult()->addValue( 'query', $property, $data );
+ }
+
+ protected function appendInstalledLibraries( $property ) {
+ global $IP;
+ $path = "$IP/composer.lock";
+ if ( !file_exists( $path ) ) {
+ // Maybe they're using mediawiki/vendor?
+ $path = "$IP/vendor/composer.lock";
+ if ( !file_exists( $path ) ) {
+ return true;
+ }
+ }
+
+ $data = array();
+ $lock = new ComposerLock( $path );
+ foreach ( $lock->getInstalledDependencies() as $name => $info ) {
+ if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
+ // Skip any extensions or skins since they'll be listed
+ // in their proper section
+ continue;
+ }
+ $data[] = array(
+ 'name' => $name,
+ 'version' => $info['version'],
+ );
+ }
+ ApiResult::setIndexedTagName( $data, 'library' );
return $this->getResult()->addValue( 'query', $property, $data );
+
}
protected function appendExtensions( $property ) {
@@ -575,7 +584,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
if ( is_array( $ext['descriptionmsg'] ) ) {
$ret['descriptionmsg'] = $ext['descriptionmsg'][0];
$ret['descriptionmsgparams'] = array_slice( $ext['descriptionmsg'], 1 );
- $this->getResult()->setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
+ ApiResult::setIndexedTagName( $ret['descriptionmsgparams'], 'param' );
} else {
$ret['descriptionmsg'] = $ext['descriptionmsg'];
}
@@ -635,15 +644,21 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
}
- $this->getResult()->setIndexedTagName( $data, 'ext' );
+ ApiResult::setIndexedTagName( $data, 'ext' );
return $this->getResult()->addValue( 'query', $property, $data );
}
protected function appendRightsInfo( $property ) {
$config = $this->getConfig();
- $title = Title::newFromText( $config->get( 'RightsPage' ) );
- $url = $title ? wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ) : $config->get( 'RightsUrl' );
+ $rightsPage = $config->get( 'RightsPage' );
+ if ( is_string( $rightsPage ) ) {
+ $title = Title::newFromText( $rightsPage );
+ $url = wfExpandUrl( $title, PROTO_CURRENT );
+ } else {
+ $title = false;
+ $url = $config->get( 'RightsUrl' );
+ }
$text = $config->get( 'RightsText' );
if ( !$text && $title ) {
$text = $title->getPrefixedText();
@@ -666,10 +681,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'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' );
+ ApiResult::setIndexedTagName( $data['types'], 'type' );
+ ApiResult::setIndexedTagName( $data['levels'], 'level' );
+ ApiResult::setIndexedTagName( $data['cascadinglevels'], 'level' );
+ ApiResult::setIndexedTagName( $data['semiprotectedlevels'], 'level' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -683,10 +698,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
foreach ( $langNames as $code => $name ) {
$lang = array( 'code' => $code );
- ApiResult::setContent( $lang, $name );
+ ApiResult::setContentValue( $lang, 'name', $name );
$data[] = $lang;
}
- $this->getResult()->setIndexedTagName( $data, 'lang' );
+ ApiResult::setIndexedTagName( $data, 'lang' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -707,16 +722,16 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$displayName = $msg->text();
}
$skin = array( 'code' => $name );
- ApiResult::setContent( $skin, $displayName );
+ ApiResult::setContentValue( $skin, 'name', $displayName );
if ( !isset( $allowed[$name] ) ) {
- $skin['unusable'] = '';
+ $skin['unusable'] = true;
}
if ( $name === $default ) {
- $skin['default'] = '';
+ $skin['default'] = true;
}
$data[] = $skin;
}
- $this->getResult()->setIndexedTagName( $data, 'skin' );
+ ApiResult::setIndexedTagName( $data, 'skin' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -725,7 +740,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
global $wgParser;
$wgParser->firstCallInit();
$tags = array_map( array( $this, 'formatParserTags' ), $wgParser->getTags() );
- $this->getResult()->setIndexedTagName( $tags, 't' );
+ ApiResult::setIndexedTagName( $tags, 't' );
return $this->getResult()->addValue( 'query', $property, $tags );
}
@@ -734,14 +749,14 @@ class ApiQuerySiteinfo extends ApiQueryBase {
global $wgParser;
$wgParser->firstCallInit();
$hooks = $wgParser->getFunctionHooks();
- $this->getResult()->setIndexedTagName( $hooks, 'h' );
+ ApiResult::setIndexedTagName( $hooks, 'h' );
return $this->getResult()->addValue( 'query', $property, $hooks );
}
public function appendVariables( $property ) {
$variables = MagicWord::getVariableIDs();
- $this->getResult()->setIndexedTagName( $variables, 'v' );
+ ApiResult::setIndexedTagName( $variables, 'v' );
return $this->getResult()->addValue( 'query', $property, $variables );
}
@@ -749,13 +764,15 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function appendProtocols( $property ) {
// Make a copy of the global so we don't try to set the _element key of it - bug 45130
$protocols = array_values( $this->getConfig()->get( 'UrlProtocols' ) );
- $this->getResult()->setIndexedTagName( $protocols, 'p' );
+ ApiResult::setIndexedTagName( $protocols, 'p' );
return $this->getResult()->addValue( 'query', $property, $protocols );
}
public function appendDefaultOptions( $property ) {
- return $this->getResult()->addValue( 'query', $property, User::getDefaultOptions() );
+ $options = User::getDefaultOptions();
+ $options[ApiResult::META_BC_BOOLS] = array_keys( $options );
+ return $this->getResult()->addValue( 'query', $property, $options );
}
private function formatParserTags( $item ) {
@@ -774,11 +791,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $subscribers ),
);
- $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
+ ApiResult::setIndexedTagName( $arr['subscribers'], 's' );
$data[] = $arr;
}
- $this->getResult()->setIndexedTagName( $data, 'hook' );
+ ApiResult::setIndexedTagName( $data, 'hook' );
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -811,6 +828,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'dbrepllag',
'statistics',
'usergroups',
+ 'libraries',
'extensions',
'fileextensions',
'rightsinfo',
@@ -837,54 +855,14 @@ class ApiQuerySiteinfo extends ApiQueryBase {
);
}
- public function getParamDescription() {
- $p = $this->getModulePrefix();
-
- return array(
- 'prop' => array(
- 'Which sysinfo properties to get:',
- ' general - Overall system information',
- ' namespaces - List of registered namespaces and their canonical names',
- ' namespacealiases - List of registered namespace aliases',
- ' specialpagealiases - List of special page aliases',
- ' magicwords - List of magic words and their aliases',
- ' statistics - Returns site statistics',
- ' 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',
- ' 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 ' .
- "(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) and skin names',
- );
- }
-
- public function getDescription() {
- return 'Return general information about the site.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics',
- 'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local',
- 'api.php?action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb=',
+ 'action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics'
+ => 'apihelp-query+siteinfo-example-simple',
+ 'action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local'
+ => 'apihelp-query+siteinfo-example-interwiki',
+ 'action=query&meta=siteinfo&siprop=dbrepllag&sishowalldb='
+ => 'apihelp-query+siteinfo-example-replag',
);
}
diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php
index db928560..11268426 100644
--- a/includes/api/ApiQueryStashImageInfo.php
+++ b/includes/api/ApiQueryStashImageInfo.php
@@ -52,18 +52,16 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
}
try {
- $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $this->getUser() );
foreach ( $params['filekey'] as $filekey ) {
$file = $stash->getFile( $filekey );
$finalThumbParam = $this->mergeThumbParams( $file, $scale, $params['urlparam'] );
$imageInfo = ApiQueryImageInfo::getInfo( $file, $prop, $result, $finalThumbParam );
$result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo );
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix );
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), $modulePrefix );
}
// @todo Update exception handling here to understand current getFile exceptions
- } catch ( UploadStashNotAvailableException $e ) {
- $this->dieUsage( "Session not available: " . $e->getMessage(), "nosession" );
} catch ( UploadStashFileNotFoundException $e ) {
$this->dieUsage( "File not found: " . $e->getMessage(), "invalidsessiondata" );
} catch ( UploadStashBadPathException $e ) {
@@ -90,50 +88,38 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'timestamp|url',
- ApiBase::PARAM_TYPE => self::getPropertyNames( $this->propertyFilter )
+ ApiBase::PARAM_TYPE => self::getPropertyNames( $this->propertyFilter ),
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+imageinfo-param-prop',
+ ApiBase::PARAM_HELP_MSG_PER_VALUE => self::getPropertyMessages( $this->propertyFilter )
),
'urlwidth' => array(
ApiBase::PARAM_TYPE => 'integer',
- ApiBase::PARAM_DFLT => -1
+ ApiBase::PARAM_DFLT => -1,
+ ApiBase::PARAM_HELP_MSG => array(
+ 'apihelp-query+imageinfo-param-urlwidth',
+ ApiQueryImageInfo::TRANSFORM_LIMIT,
+ ),
),
'urlheight' => array(
ApiBase::PARAM_TYPE => 'integer',
- ApiBase::PARAM_DFLT => -1
+ ApiBase::PARAM_DFLT => -1,
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+imageinfo-param-urlheight',
),
'urlparam' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_DFLT => '',
+ ApiBase::PARAM_HELP_MSG => 'apihelp-query+imageinfo-param-urlparam',
),
);
}
- /**
- * Return the API documentation for the parameters.
- * @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.',
- 'sessionkey' => 'Alias for filekey, for backward compatibility.',
- 'urlwidth' => "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
- 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$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" ),
- );
- }
-
- public function getDescription() {
- return 'Returns image information for stashed images.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&prop=stashimageinfo&siifilekey=124sd34rsdf567',
- 'api.php?action=query&prop=stashimageinfo&siifilekey=b34edoe3|bceffd4&' .
- 'siiurlwidth=120&siiprop=url',
+ 'action=query&prop=stashimageinfo&siifilekey=124sd34rsdf567'
+ => 'apihelp-query+stashimageinfo-example-simple',
+ 'action=query&prop=stashimageinfo&siifilekey=b34edoe3|bceffd4&' .
+ 'siiurlwidth=120&siiprop=url'
+ => 'apihelp-query+stashimageinfo-example-params',
);
}
}
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index 31845648..45f73b20 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -31,15 +31,6 @@
*/
class ApiQueryTags extends ApiQueryBase {
- /**
- * @var ApiResult
- */
- private $result;
-
- private $limit;
- private $fld_displayname = false, $fld_description = false,
- $fld_hitcount = false;
-
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'tg' );
}
@@ -49,84 +40,100 @@ class ApiQueryTags extends ApiQueryBase {
$prop = array_flip( $params['prop'] );
- $this->fld_displayname = isset( $prop['displayname'] );
- $this->fld_description = isset( $prop['description'] );
- $this->fld_hitcount = isset( $prop['hitcount'] );
-
- $this->limit = $params['limit'];
- $this->result = $this->getResult();
+ $fld_displayname = isset( $prop['displayname'] );
+ $fld_description = isset( $prop['description'] );
+ $fld_hitcount = isset( $prop['hitcount'] );
+ $fld_defined = isset( $prop['defined'] );
+ $fld_source = isset( $prop['source'] );
+ $fld_active = isset( $prop['active'] );
+
+ $limit = $params['limit'];
+ $result = $this->getResult();
+
+ $extensionDefinedTags = array_fill_keys( ChangeTags::listExtensionDefinedTags(), 0 );
+ $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
+ $extensionActivatedTags = array_fill_keys( ChangeTags::listExtensionActivatedTags(), 0 );
+
+ $definedTags = array_merge( $extensionDefinedTags, $explicitlyDefinedTags );
+
+ # Fetch defined tags that aren't past the continuation
+ if ( $params['continue'] !== null ) {
+ $cont = $params['continue'];
+ $tags = array_filter( array_keys( $definedTags ), function ( $v ) use ( $cont ) {
+ return $v >= $cont;
+ } );
+ $tags = array_fill_keys( $tags, 0 );
+ } else {
+ $tags = $definedTags;
+ }
+ # Merge in all used tags
$this->addTables( 'change_tag' );
$this->addFields( 'ct_tag' );
-
- $this->addFieldsIf( array( 'hitcount' => 'COUNT(*)' ), $this->fld_hitcount );
-
- $this->addOption( 'LIMIT', $this->limit + 1 );
+ $this->addFields( array( 'hitcount' => $fld_hitcount ? 'COUNT(*)' : '0' ) );
+ $this->addOption( 'LIMIT', $limit + 1 );
$this->addOption( 'GROUP BY', 'ct_tag' );
$this->addWhereRange( 'ct_tag', 'newer', $params['continue'], null );
-
$res = $this->select( __METHOD__ );
-
- $ok = true;
-
foreach ( $res as $row ) {
- if ( !$ok ) {
- break;
- }
- $ok = $this->doTag( $row->ct_tag, $this->fld_hitcount ? $row->hitcount : 0 );
+ $tags[$row->ct_tag] = (int)$row->hitcount;
}
- // include tags with no hits yet
- foreach ( ChangeTags::listDefinedTags() as $tag ) {
- if ( !$ok ) {
+ # Now make sure the array is sorted for proper continuation
+ ksort( $tags );
+
+ $count = 0;
+ foreach ( $tags as $tagName => $hitcount ) {
+ if ( ++$count > $limit ) {
+ $this->setContinueEnumParameter( 'continue', $tagName );
break;
}
- $ok = $this->doTag( $tag, 0 );
- }
-
- $this->result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'tag' );
- }
- private function doTag( $tagName, $hitcount ) {
- static $count = 0;
- static $doneTags = array();
+ $tag = array();
+ $tag['name'] = $tagName;
- if ( in_array( $tagName, $doneTags ) ) {
- return true;
- }
-
- if ( ++$count > $this->limit ) {
- $this->setContinueEnumParameter( 'continue', $tagName );
-
- return false;
- }
+ if ( $fld_displayname ) {
+ $tag['displayname'] = ChangeTags::tagDescription( $tagName );
+ }
- $tag = array();
- $tag['name'] = $tagName;
+ if ( $fld_description ) {
+ $msg = $this->msg( "tag-$tagName-description" );
+ $tag['description'] = $msg->exists() ? $msg->text() : '';
+ }
- if ( $this->fld_displayname ) {
- $tag['displayname'] = ChangeTags::tagDescription( $tagName );
- }
+ if ( $fld_hitcount ) {
+ $tag['hitcount'] = $hitcount;
+ }
- if ( $this->fld_description ) {
- $msg = wfMessage( "tag-$tagName-description" );
- $tag['description'] = $msg->exists() ? $msg->text() : '';
- }
+ $isExtension = isset( $extensionDefinedTags[$tagName] );
+ $isExplicit = isset( $explicitlyDefinedTags[$tagName] );
- if ( $this->fld_hitcount ) {
- $tag['hitcount'] = $hitcount;
- }
+ if ( $fld_defined ) {
+ $tag['defined'] = $isExtension || $isExplicit;
+ }
- $doneTags[] = $tagName;
+ if ( $fld_source ) {
+ $tag['source'] = array();
+ if ( $isExtension ) {
+ $tag['source'][] = 'extension';
+ }
+ if ( $isExplicit ) {
+ $tag['source'][] = 'manual';
+ }
+ }
- $fit = $this->result->addValue( array( 'query', $this->getModuleName() ), null, $tag );
- if ( !$fit ) {
- $this->setContinueEnumParameter( 'continue', $tagName );
+ if ( $fld_active ) {
+ $tag['active'] = $isExplicit || isset( $extensionActivatedTags[$tagName] );
+ }
- return false;
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $tag );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', $tagName );
+ break;
+ }
}
- return true;
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'tag' );
}
public function getCacheMode( $params ) {
@@ -135,7 +142,9 @@ class ApiQueryTags extends ApiQueryBase {
public function getAllowedParams() {
return array(
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -149,34 +158,20 @@ class ApiQueryTags extends ApiQueryBase {
'name',
'displayname',
'description',
- 'hitcount'
+ 'hitcount',
+ 'defined',
+ 'source',
+ 'active',
),
ApiBase::PARAM_ISMULTI => true
)
);
}
- public function getParamDescription() {
- return array(
- 'continue' => 'When more results are available, use this to continue',
- 'limit' => 'The maximum number of tags to list',
- 'prop' => array(
- 'Which properties to get',
- ' name - Adds name of tag',
- ' displayname - Adds system message for the tag',
- ' description - Adds description of the tag',
- ' hitcount - Adds the amount of revisions that have this tag',
- ),
- );
- }
-
- public function getDescription() {
- return 'List change tags.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
+ 'action=query&list=tags&tgprop=displayname|description|hitcount|defined'
+ => 'apihelp-query+tags-example-simple',
);
}
diff --git a/includes/api/ApiQueryTokens.php b/includes/api/ApiQueryTokens.php
index ba9c9377..65a08a3b 100644
--- a/includes/api/ApiQueryTokens.php
+++ b/includes/api/ApiQueryTokens.php
@@ -35,10 +35,12 @@ class ApiQueryTokens extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
- $res = array();
+ $res = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
- if ( $this->getMain()->getRequest()->getVal( 'callback' ) !== null ) {
- $this->setWarning( 'Tokens may not be obtained when using a callback' );
+ if ( $this->lacksSameOriginSecurity() ) {
+ $this->setWarning( 'Tokens may not be obtained when the same-origin policy is not applied' );
return;
}
@@ -55,7 +57,6 @@ class ApiQueryTokens extends ApiQueryBase {
public static function getTokenTypeSalts() {
static $salts = null;
if ( !$salts ) {
- wfProfileIn( __METHOD__ );
$salts = array(
'csrf' => '',
'watch' => 'watch',
@@ -63,9 +64,8 @@ class ApiQueryTokens extends ApiQueryBase {
'rollback' => 'rollback',
'userrights' => 'userrights',
);
- wfRunHooks( 'ApiQueryTokensRegisterTypes', array( &$salts ) );
+ Hooks::run( 'ApiQueryTokensRegisterTypes', array( &$salts ) );
ksort( $salts );
- wfProfileOut( __METHOD__ );
}
return $salts;
@@ -81,20 +81,12 @@ class ApiQueryTokens extends ApiQueryBase {
);
}
- 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() {
+ protected function getExamplesMessages() {
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'
+ 'action=query&meta=tokens'
+ => 'apihelp-query+tokens-example-simple',
+ 'action=query&meta=tokens&type=watch|patrol'
+ => 'apihelp-query+tokens-example-types',
);
}
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index 4b167b8b..e5ec67d0 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -120,7 +120,7 @@ class ApiQueryContributions extends ApiQueryBase {
}
}
- $this->getResult()->setIndexedTagName_internal(
+ $this->getResult()->addIndexedTagName(
array( 'query', $this->getModuleName() ),
'item'
);
@@ -335,7 +335,7 @@ class ApiQueryContributions extends ApiQueryBase {
$anyHidden = false;
if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
- $vals['texthidden'] = '';
+ $vals['texthidden'] = true;
$anyHidden = true;
}
@@ -343,7 +343,7 @@ class ApiQueryContributions extends ApiQueryBase {
$vals['userid'] = $row->rev_user;
$vals['user'] = $row->rev_user_text;
if ( $row->rev_deleted & Revision::DELETED_USER ) {
- $vals['userhidden'] = '';
+ $vals['userhidden'] = true;
$anyHidden = true;
}
if ( $this->fld_ids ) {
@@ -367,20 +367,14 @@ class ApiQueryContributions extends ApiQueryBase {
}
if ( $this->fld_flags ) {
- if ( $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id ) ) {
- $vals['new'] = '';
- }
- if ( $row->rev_minor_edit ) {
- $vals['minor'] = '';
- }
- if ( $row->page_latest == $row->rev_id ) {
- $vals['top'] = '';
- }
+ $vals['new'] = $row->rev_parent_id == 0 && !is_null( $row->rev_parent_id );
+ $vals['minor'] = (bool)$row->rev_minor_edit;
+ $vals['top'] = $row->page_latest == $row->rev_id;
}
if ( ( $this->fld_comment || $this->fld_parsedcomment ) && isset( $row->rev_comment ) ) {
if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
- $vals['commenthidden'] = '';
+ $vals['commenthidden'] = true;
$anyHidden = true;
}
@@ -400,8 +394,8 @@ class ApiQueryContributions extends ApiQueryBase {
}
}
- if ( $this->fld_patrolled && $row->rc_patrolled ) {
- $vals['patrolled'] = '';
+ if ( $this->fld_patrolled ) {
+ $vals['patrolled'] = (bool)$row->rc_patrolled;
}
if ( $this->fld_size && !is_null( $row->rev_len ) ) {
@@ -421,7 +415,7 @@ class ApiQueryContributions extends ApiQueryBase {
if ( $this->fld_tags ) {
if ( $row->ts_tags ) {
$tags = explode( ',', $row->ts_tags );
- $this->getResult()->setIndexedTagName( $tags, 'tag' );
+ ApiResult::setIndexedTagName( $tags, 'tag' );
$vals['tags'] = $tags;
} else {
$vals['tags'] = array();
@@ -429,7 +423,7 @@ class ApiQueryContributions extends ApiQueryBase {
}
if ( $anyHidden && $row->rev_deleted & Revision::DELETED_RESTRICTED ) {
- $vals['suppressed'] = '';
+ $vals['suppressed'] = true;
}
return $vals;
@@ -464,7 +458,9 @@ class ApiQueryContributions extends ApiQueryBase {
'end' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'user' => array(
ApiBase::PARAM_ISMULTI => true
),
@@ -474,7 +470,8 @@ class ApiQueryContributions extends ApiQueryBase {
ApiBase::PARAM_TYPE => array(
'newer',
'older'
- )
+ ),
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
),
'namespace' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -507,7 +504,11 @@ class ApiQueryContributions extends ApiQueryBase {
'!top',
'new',
'!new',
- )
+ ),
+ ApiBase::PARAM_HELP_MSG => array(
+ 'apihelp-query+usercontribs-param-show',
+ $this->getConfig()->get( 'RCMaxAge' )
+ ),
),
'tag' => null,
'toponly' => array(
@@ -517,53 +518,12 @@ class ApiQueryContributions extends ApiQueryBase {
);
}
- public function getParamDescription() {
- $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' => 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(
- 'Include additional pieces of information',
- ' ids - Adds the page ID and revision ID',
- ' title - Adds the title and namespace ID of the page',
- ' timestamp - Adds the timestamp of the edit',
- ' comment - Adds the comment of the edit',
- ' parsedcomment - Adds the parsed comment of the edit',
- ' size - Adds the new size of the edit',
- ' sizediff - Adds the size delta of the edit against its parent',
- ' flags - Adds flags of the edit',
- ' patrolled - Tags patrolled edits',
- ' tags - Lists tags for the edit',
- ),
- '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 getDescription() {
- return 'Get all edits by a user.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=usercontribs&ucuser=YurikBot',
- 'api.php?action=query&list=usercontribs&ucuserprefix=217.121.114.',
+ 'action=query&list=usercontribs&ucuser=Example'
+ => 'apihelp-query+usercontribs-example-user',
+ 'action=query&list=usercontribs&ucuserprefix=192.0.2.'
+ => 'apihelp-query+usercontribs-example-ipprefix',
);
}
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index fd5f47b4..4a7d1e0f 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -59,7 +59,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['name'] = $user->getName();
if ( $user->isAnon() ) {
- $vals['anon'] = '';
+ $vals['anon'] = true;
}
if ( isset( $this->prop['blockinfo'] ) ) {
@@ -76,36 +76,40 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
}
- if ( isset( $this->prop['hasmsg'] ) && $user->getNewtalk() ) {
- $vals['messages'] = '';
+ if ( isset( $this->prop['hasmsg'] ) ) {
+ $vals['messages'] = $user->getNewtalk();
}
if ( isset( $this->prop['groups'] ) ) {
$vals['groups'] = $user->getEffectiveGroups();
- $result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
+ ApiResult::setArrayType( $vals['groups'], 'array' ); // even if empty
+ ApiResult::setIndexedTagName( $vals['groups'], 'g' ); // even if empty
}
if ( isset( $this->prop['implicitgroups'] ) ) {
$vals['implicitgroups'] = $user->getAutomaticGroups();
- $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
+ ApiResult::setArrayType( $vals['implicitgroups'], 'array' ); // even if empty
+ ApiResult::setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
}
if ( isset( $this->prop['rights'] ) ) {
// User::getRights() may return duplicate values, strip them
$vals['rights'] = array_values( array_unique( $user->getRights() ) );
- $result->setIndexedTagName( $vals['rights'], 'r' ); // even if empty
+ ApiResult::setArrayType( $vals['rights'], 'array' ); // even if empty
+ ApiResult::setIndexedTagName( $vals['rights'], 'r' ); // even if empty
}
if ( isset( $this->prop['changeablegroups'] ) ) {
$vals['changeablegroups'] = $user->changeableGroups();
- $result->setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
- $result->setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
- $result->setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
- $result->setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
+ ApiResult::setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
+ ApiResult::setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
+ ApiResult::setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
+ ApiResult::setIndexedTagName( $vals['changeablegroups']['remove-self'], 'g' );
}
if ( isset( $this->prop['options'] ) ) {
$vals['options'] = $user->getOptions();
+ $vals['options'][ApiResult::META_BC_BOOLS] = array_keys( $vals['options'] );
}
if ( isset( $this->prop['preferencestoken'] ) ) {
@@ -115,7 +119,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
);
}
if ( isset( $this->prop['preferencestoken'] ) &&
- is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) &&
+ !$this->lacksSameOriginSecurity() &&
$user->isAllowed( 'editmyoptions' )
) {
$vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
@@ -157,19 +161,19 @@ class ApiQueryUserInfo extends ApiQueryBase {
$acceptLang = array();
foreach ( $langs as $lang => $val ) {
$r = array( 'q' => $val );
- ApiResult::setContent( $r, $lang );
+ ApiResult::setContentValue( $r, 'code', $lang );
$acceptLang[] = $r;
}
- $result->setIndexedTagName( $acceptLang, 'lang' );
+ ApiResult::setIndexedTagName( $acceptLang, 'lang' );
$vals['acceptlang'] = $acceptLang;
}
if ( isset( $this->prop['unreadcount'] ) ) {
$dbr = $this->getQuery()->getNamedDB( 'watchlist', DB_SLAVE, 'watchlist' );
- $sql = $dbr->selectSQLText(
+ $count = $dbr->selectRowCount(
'watchlist',
- array( 'dummy' => 1 ),
+ '1',
array(
'wl_user' => $user->getId(),
'wl_notificationtimestamp IS NOT NULL',
@@ -177,12 +181,11 @@ class ApiQueryUserInfo extends ApiQueryBase {
__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;
+ $vals['unreadcount'] = $count;
}
}
@@ -190,9 +193,13 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
protected function getRateLimits() {
+ $retval = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
+
$user = $this->getUser();
if ( !$user->isPingLimitable() ) {
- return array(); // No limits
+ return $retval; // No limits
}
// Find out which categories we belong to
@@ -212,7 +219,6 @@ class ApiQueryUserInfo extends ApiQueryBase {
$categories = array_merge( $categories, $user->getGroups() );
// Now get the actual limits
- $retval = array();
foreach ( $this->getConfig()->get( 'RateLimits' ) as $action => $limits ) {
foreach ( $categories as $cat ) {
if ( isset( $limits[$cat] ) && !is_null( $limits[$cat] ) ) {
@@ -246,45 +252,22 @@ class ApiQueryUserInfo extends ApiQueryBase {
'acceptlang',
'registrationdate',
'unreadcount',
- )
- )
- );
- }
-
- public function getParamDescription() {
- return array(
- 'prop' => array(
- 'What pieces of information to include',
- ' blockinfo - Tags if the current user is blocked, by whom, and for what reason',
- ' hasmsg - Adds a tag "message" if the current user has pending messages',
- ' groups - Lists all the groups the current user belongs to',
- ' implicitgroups - Lists all the groups the current user is automatically a member of',
- ' 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 - 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',
- ' registrationdate - Adds the user\'s registration date',
- ' 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)',
+ ),
+ ApiBase::PARAM_HELP_MSG => array(
+ 'apihelp-query+userinfo-param-prop',
+ self::WL_UNREAD_LIMIT - 1,
+ self::WL_UNREAD_LIMIT . '+',
+ ),
)
);
}
- public function getDescription() {
- return 'Get information about the current user.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&meta=userinfo',
- 'api.php?action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg',
+ 'action=query&meta=userinfo'
+ => 'apihelp-query+userinfo-example-simple',
+ 'action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg'
+ => 'apihelp-query+userinfo-example-data',
);
}
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index 2f5e4b4b..f22c2134 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -67,15 +67,16 @@ class ApiQueryUsers extends ApiQueryBase {
return $this->tokenFunctions;
}
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
return array();
}
$this->tokenFunctions = array(
'userrights' => array( 'ApiQueryUsers', 'getUserrightsToken' ),
);
- wfRunHooks( 'APIQueryUsersTokens', array( &$this->tokenFunctions ) );
+ Hooks::run( 'APIQueryUsersTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
}
@@ -109,7 +110,7 @@ class ApiQueryUsers extends ApiQueryBase {
foreach ( $users as $u ) {
$n = User::getCanonicalName( $u );
if ( $n === false || $n === '' ) {
- $vals = array( 'name' => $u, 'invalid' => '' );
+ $vals = array( 'name' => $u, 'invalid' => true );
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $vals );
if ( !$fit ) {
@@ -189,7 +190,7 @@ class ApiQueryUsers extends ApiQueryBase {
$data[$name]['rights'] = $user->getRights();
}
if ( $row->ipb_deleted ) {
- $data[$name]['hidden'] = '';
+ $data[$name]['hidden'] = true;
}
if ( isset( $this->prop['blockinfo'] ) && !is_null( $row->ipb_by_text ) ) {
$data[$name]['blockid'] = $row->ipb_id;
@@ -200,8 +201,8 @@ class ApiQueryUsers extends ApiQueryBase {
$data[$name]['blockexpiry'] = $row->ipb_expiry;
}
- if ( isset( $this->prop['emailable'] ) && $user->canReceiveEmail() ) {
- $data[$name]['emailable'] = '';
+ if ( isset( $this->prop['emailable'] ) ) {
+ $data[$name]['emailable'] = $user->canReceiveEmail();
}
if ( isset( $this->prop['gender'] ) ) {
@@ -236,7 +237,7 @@ class ApiQueryUsers extends ApiQueryBase {
$iwUser = $urPage->fetchUser( $u );
if ( $iwUser instanceof UserRightsProxy ) {
- $data[$u]['interwiki'] = '';
+ $data[$u]['interwiki'] = true;
if ( !is_null( $params['token'] ) ) {
$tokenFunctions = $this->getTokenFunctions();
@@ -251,17 +252,20 @@ class ApiQueryUsers extends ApiQueryBase {
}
}
} else {
- $data[$u]['missing'] = '';
+ $data[$u]['missing'] = true;
}
} else {
if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) {
- $result->setIndexedTagName( $data[$u]['groups'], 'g' );
+ ApiResult::setArrayType( $data[$u]['groups'], 'array' );
+ ApiResult::setIndexedTagName( $data[$u]['groups'], 'g' );
}
if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
- $result->setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
+ ApiResult::setArrayType( $data[$u]['implicitgroups'], 'array' );
+ ApiResult::setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
}
if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
- $result->setIndexedTagName( $data[$u]['rights'], 'r' );
+ ApiResult::setArrayType( $data[$u]['rights'], 'array' );
+ ApiResult::setIndexedTagName( $data[$u]['rights'], 'r' );
}
}
@@ -274,20 +278,7 @@ class ApiQueryUsers extends ApiQueryBase {
}
$done[] = $u;
}
- $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
- }
-
- /**
- * 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
- * @return array
- */
- public static function getAutoGroups( $user ) {
- wfDeprecated( __METHOD__, '1.20' );
-
- return $user->getAutomaticGroups();
+ $result->addIndexedTagName( array( 'query', $this->getModuleName() ), 'user' );
}
public function getCacheMode( $params ) {
@@ -327,33 +318,13 @@ class ApiQueryUsers extends ApiQueryBase {
);
}
- public function getParamDescription() {
+ protected function getExamplesMessages() {
return array(
- 'prop' => array(
- 'What pieces of information to include',
- ' blockinfo - Tags if the user is blocked, by whom, and for what reason',
- ' groups - Lists all the groups the user(s) belongs to',
- ' implicitgroups - Lists all the groups a user is automatically a member of',
- ' 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]]',
- ' gender - Tags the gender of the user. Returns "male", "female", or "unknown"',
- ),
- 'users' => 'A list of users to obtain the same information for',
- 'token' => 'Which tokens to obtain for each user',
+ 'action=query&list=users&ususers=Example&usprop=groups|editcount|gender'
+ => 'apihelp-query+users-example-simple',
);
}
- public function getDescription() {
- return 'Get information about a list of users.';
- }
-
- public function getExamples() {
- return 'api.php?action=query&list=users&ususers=brion|TimStarling&usprop=groups|editcount|gender';
- }
-
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Users';
}
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index efbe05ee..9f7387c7 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -199,7 +199,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ( !is_null( $params['type'] ) ) {
try {
$this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
ApiBase::dieDebug( __METHOD__, $e->getMessage() );
}
}
@@ -281,7 +281,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $this->getResult()->setIndexedTagName_internal(
+ $this->getResult()->addIndexedTagName(
array( 'query', $this->getModuleName() ),
'item'
);
@@ -307,7 +307,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
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'] = '';
+ $vals['actionhidden'] = true;
$anyHidden = true;
}
if ( $type !== RC_LOG ||
@@ -327,7 +327,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
/* Add user data and 'anon' flag, if user is anonymous. */
if ( $this->fld_user || $this->fld_userid ) {
if ( $row->rc_deleted & Revision::DELETED_USER ) {
- $vals['userhidden'] = '';
+ $vals['userhidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_USER, $user ) ) {
@@ -342,22 +342,16 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
if ( !$row->rc_user ) {
- $vals['anon'] = '';
+ $vals['anon'] = true;
}
}
}
/* 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'] = '';
- }
+ $vals['bot'] = (bool)$row->rc_bot;
+ $vals['new'] = $row->rc_type == RC_NEW;
+ $vals['minor'] = (bool)$row->rc_minor;
}
/* Add sizes of each revision. (Only available on 1.10+) */
@@ -380,7 +374,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
/* Add edit summary / log summary. */
if ( $this->fld_comment || $this->fld_parsedcomment ) {
if ( $row->rc_deleted & Revision::DELETED_COMMENT ) {
- $vals['commenthidden'] = '';
+ $vals['commenthidden'] = true;
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->rc_deleted, Revision::DELETED_COMMENT, $user ) ) {
@@ -395,37 +389,26 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
/* Add the patrolled flag */
- if ( $this->fld_patrol && $row->rc_patrolled == 1 ) {
- $vals['patrolled'] = '';
- }
-
- if ( $this->fld_patrol && ChangesList::isUnpatrolled( $row, $user ) ) {
- $vals['unpatrolled'] = '';
+ if ( $this->fld_patrol ) {
+ $vals['patrolled'] = $row->rc_patrolled == 1;
+ $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
}
if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
- $vals['actionhidden'] = '';
+ $vals['actionhidden'] = true;
$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()
- );
+ $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
}
}
if ( $anyHidden && ( $row->rc_deleted & Revision::DELETED_RESTRICTED ) ) {
- $vals['suppressed'] = '';
+ $vals['suppressed'] = true;
}
return $vals;
@@ -455,7 +438,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
ApiBase::PARAM_TYPE => array(
'newer',
'older'
- )
+ ),
+ ApiHelp::PARAM_HELP_MSG => 'api-help-param-direction',
),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -498,6 +482,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
)
),
'type' => array(
+ ApiBase::PARAM_DFLT => 'edit|new|log',
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
'edit',
@@ -512,67 +497,26 @@ 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',
- 'end' => 'The timestamp to end enumerating',
- 'namespace' => 'Filter changes to only the given namespace(s)',
- 'user' => 'Only list changes by this user',
- 'excludeuser' => 'Don\'t list changes by this user',
- 'dir' => $this->getDirectionDescription( $p ),
- 'limit' => 'How many total results to return per request',
- 'prop' => array(
- 'Which additional items to get (non-generator mode only).',
- ' ids - Adds revision ids and page ids',
- ' title - Adds title of the page',
- ' flags - Adds flags for the edit',
- ' user - Adds the user who made the edit',
- ' userid - Adds user id of whom made the edit',
- ' comment - Adds comment of the edit',
- ' parsedcomment - Adds parsed comment of the edit',
- ' timestamp - Adds timestamp of the edit',
- ' patrol - Tags edits that are patrolled',
- ' sizes - Adds the old and new lengths of the page',
- ' notificationtimestamp - Adds timestamp of when the user was last notified about the edit',
- ' loginfo - Adds log information where appropriate',
- ),
- 'show' => array(
- 'Show only items that meet this criteria.',
- "For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon"
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
),
- 'type' => array(
- 'Which types of changes to show',
- ' edit - Regular page edits',
- ' external - External changes',
- ' new - Page creations',
- ' 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',
- '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 getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=watchlist',
- 'api.php?action=query&list=watchlist&wlprop=ids|title|timestamp|user|comment',
- 'api.php?action=query&list=watchlist&wlallrev=&wlprop=ids|title|timestamp|user|comment',
- 'api.php?action=query&generator=watchlist&prop=info',
- 'api.php?action=query&generator=watchlist&gwlallrev=&prop=revisions&rvprop=timestamp|user',
- 'api.php?action=query&list=watchlist&wlowner=Bob_Smith&wltoken=123ABC'
+ 'action=query&list=watchlist'
+ => 'apihelp-query+watchlist-example-simple',
+ 'action=query&list=watchlist&wlprop=ids|title|timestamp|user|comment'
+ => 'apihelp-query+watchlist-example-props',
+ 'action=query&list=watchlist&wlallrev=&wlprop=ids|title|timestamp|user|comment'
+ => 'apihelp-query+watchlist-example-allrev',
+ 'action=query&generator=watchlist&prop=info'
+ => 'apihelp-query+watchlist-example-generator',
+ 'action=query&generator=watchlist&gwlallrev=&prop=revisions&rvprop=timestamp|user'
+ => 'apihelp-query+watchlist-example-generator-rev',
+ 'action=query&list=watchlist&wlowner=Example&wltoken=123ABC'
+ => 'apihelp-query+watchlist-example-wlowner',
);
}
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index 6b2223ac..493c192a 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -123,7 +123,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
}
}
if ( is_null( $resultPageSet ) ) {
- $this->getResult()->setIndexedTagName_internal( $this->getModuleName(), 'wr' );
+ $this->getResult()->addIndexedTagName( $this->getModuleName(), 'wr' );
} else {
$resultPageSet->populateFromTitles( $titles );
}
@@ -131,7 +131,9 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
public function getAllowedParams() {
return array(
- 'continue' => null,
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
'namespace' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => 'namespace'
@@ -168,35 +170,17 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
'ascending',
'descending'
),
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
),
);
}
- public function getParamDescription() {
- return array(
- 'continue' => 'When more results are available, use this to continue',
- 'namespace' => 'Only list pages in the given namespace(s)',
- 'limit' => 'How many total results to return per request',
- 'prop' => array(
- 'Which additional properties to get (non-generator mode only)',
- ' changed - Adds timestamp of when the user was last notified about the edit',
- ),
- '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',
- 'dir' => 'Direction to sort the titles and namespaces in',
- );
- }
-
- public function getDescription() {
- return "Get all pages on the logged in user's watchlist.";
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=query&list=watchlistraw',
- 'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions',
+ 'action=query&list=watchlistraw'
+ => 'apihelp-query+watchlistraw-example-simple',
+ 'action=query&generator=watchlistraw&gwrshow=changed&prop=info'
+ => 'apihelp-query+watchlistraw-example-generator',
);
}
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 2e80447e..7c573a8f 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -1,11 +1,5 @@
<?php
/**
- *
- *
- * Created on Sep 4, 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
@@ -33,156 +27,266 @@
* 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 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.
- *
+ * @since 1.25 this is no longer a subclass of ApiBase
* @ingroup API
*/
-class ApiResult extends ApiBase {
+class ApiResult implements ApiSerializable {
/**
- * override existing value in addValue() and setElement()
+ * Override existing value in addValue(), setValue(), and similar functions
* @since 1.21
*/
const OVERRIDE = 1;
/**
- * For addValue() and setElement(), if the value does not exist, add it as the first element.
- * In case the new value has no name (numerical index), all indexes will be renumbered.
+ * For addValue(), setValue() and similar functions, if the value does not
+ * exist, add it as the first element. In case the new value has no name
+ * (numerical index), all indexes will be renumbered.
* @since 1.21
*/
const ADD_ON_TOP = 2;
/**
- * For addValue() and setElement(), do not check size while adding a value
+ * For addValue() and similar functions, 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
+ * Values added while the size checking was disabled will never be counted.
+ * Ignored for setValue() and similar functions.
* @since 1.24
*/
const NO_SIZE_CHECK = 4;
- private $mData, $mIsRawMode, $mSize, $mCheckingSize;
+ /**
+ * For addValue(), setValue() and similar functions, do not validate data.
+ * Also disables size checking. If you think you need to use this, you're
+ * probably wrong.
+ * @since 1.25
+ */
+ const NO_VALIDATE = 12;
- private $continueAllModules = array();
- private $continueGeneratedModules = array();
- private $continuationData = array();
- private $generatorContinuationData = array();
- private $generatorParams = array();
- private $generatorDone = false;
+ /**
+ * Key for the 'indexed tag name' metadata item. Value is string.
+ * @since 1.25
+ */
+ const META_INDEXED_TAG_NAME = '_element';
/**
- * @param ApiMain $main
+ * Key for the 'subelements' metadata item. Value is string[].
+ * @since 1.25
*/
- public function __construct( ApiMain $main ) {
- parent::__construct( $main, 'result' );
- $this->mIsRawMode = false;
- $this->mCheckingSize = true;
- $this->reset();
- }
+ const META_SUBELEMENTS = '_subelements';
/**
- * Clear the current result data.
+ * Key for the 'preserve keys' metadata item. Value is string[].
+ * @since 1.25
*/
- public function reset() {
- $this->mData = array();
- $this->mSize = 0;
- }
+ const META_PRESERVE_KEYS = '_preservekeys';
/**
- * 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
+ * Key for the 'content' metadata item. Value is string.
+ * @since 1.25
*/
- public function setRawMode( $flag = true ) {
- $this->mIsRawMode = $flag;
- }
+ const META_CONTENT = '_content';
/**
- * Returns true whether the formatter requested raw data.
- * @return bool
+ * Key for the 'type' metadata item. Value is one of the following strings:
+ * - default: Like 'array' if all (non-metadata) keys are numeric with no
+ * gaps, otherwise like 'assoc'.
+ * - array: Keys are used for ordering, but are not output. In a format
+ * like JSON, outputs as [].
+ * - assoc: In a format like JSON, outputs as {}.
+ * - kvp: For a format like XML where object keys have a restricted
+ * character set, use an alternative output format. For example,
+ * <container><item name="key">value</item></container> rather than
+ * <container key="value" />
+ * - BCarray: Like 'array' normally, 'default' in backwards-compatibility mode.
+ * - BCassoc: Like 'assoc' normally, 'default' in backwards-compatibility mode.
+ * - BCkvp: Like 'kvp' normally. In backwards-compatibility mode, forces
+ * the alternative output format for all formats, for example
+ * [{"name":key,"*":value}] in JSON. META_KVP_KEY_NAME must also be set.
+ * @since 1.25
*/
- public function getIsRawMode() {
- return $this->mIsRawMode;
- }
+ const META_TYPE = '_type';
/**
- * Get the result's internal data array (read-only)
- * @return array
+ * Key (rather than "name" or other default) for when META_TYPE is 'kvp' or
+ * 'BCkvp'. Value is string.
+ * @since 1.25
*/
- public function getData() {
- return $this->mData;
- }
+ const META_KVP_KEY_NAME = '_kvpkeyname';
/**
- * 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 mixed $value
- * @return int
+ * Key for the 'BC bools' metadata item. Value is string[].
+ * Note no setter is provided.
+ * @since 1.25
*/
- public static function size( $value ) {
- $s = 0;
- if ( is_array( $value ) ) {
- foreach ( $value as $v ) {
- $s += self::size( $v );
- }
- } elseif ( !is_object( $value ) ) {
- // Objects can't always be cast to string
- $s = strlen( $value );
+ const META_BC_BOOLS = '_BC_bools';
+
+ /**
+ * Key for the 'BC subelements' metadata item. Value is string[].
+ * Note no setter is provided.
+ * @since 1.25
+ */
+ const META_BC_SUBELEMENTS = '_BC_subelements';
+
+ private $data, $size, $maxSize;
+ private $errorFormatter;
+
+ // Deprecated fields
+ private $isRawMode, $checkingSize, $mainForContinuation;
+
+ /**
+ * @param int|bool $maxSize Maximum result "size", or false for no limit
+ * @since 1.25 Takes an integer|bool rather than an ApiMain
+ */
+ public function __construct( $maxSize ) {
+ if ( $maxSize instanceof ApiMain ) {
+ wfDeprecated( 'ApiMain to ' . __METHOD__, '1.25' );
+ $this->errorFormatter = $maxSize->getErrorFormatter();
+ $this->mainForContinuation = $maxSize;
+ $maxSize = $maxSize->getConfig()->get( 'APIMaxResultSize' );
}
- return $s;
+ $this->maxSize = $maxSize;
+ $this->isRawMode = false;
+ $this->checkingSize = true;
+ $this->reset();
}
/**
- * Get the size of the result, i.e. the amount of bytes in it
- * @return int
+ * Set the error formatter
+ * @since 1.25
+ * @param ApiErrorFormatter $formatter
*/
- public function getSize() {
- return $this->mSize;
+ public function setErrorFormatter( ApiErrorFormatter $formatter ) {
+ $this->errorFormatter = $formatter;
}
/**
- * 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
+ * Allow for adding one ApiResult into another
+ * @since 1.25
+ * @return mixed
*/
- public function disableSizeCheck() {
- $this->mCheckingSize = false;
+ public function serializeForApiResult() {
+ return $this->data;
}
+ /************************************************************************//**
+ * @name Content
+ * @{
+ */
+
/**
- * Re-enable size checking in addValue()
- * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
+ * Clear the current result data.
*/
- public function enableSizeCheck() {
- $this->mCheckingSize = true;
+ public function reset() {
+ $this->data = array(
+ self::META_TYPE => 'assoc', // Usually what's desired
+ );
+ $this->size = 0;
+ }
+
+ /**
+ * Get the result data array
+ *
+ * The returned value should be considered read-only.
+ *
+ * Transformations include:
+ *
+ * Custom: (callable) Applied before other transformations. Signature is
+ * function ( &$data, &$metadata ), return value is ignored. Called for
+ * each nested array.
+ *
+ * BC: (array) This transformation does various adjustments to bring the
+ * output in line with the pre-1.25 result format. The value array is a
+ * list of flags: 'nobool', 'no*', 'nosub'.
+ * - Boolean-valued items are changed to '' if true or removed if false,
+ * unless listed in META_BC_BOOLS. This may be skipped by including
+ * 'nobool' in the value array.
+ * - The tag named by META_CONTENT is renamed to '*', and META_CONTENT is
+ * set to '*'. This may be skipped by including 'no*' in the value
+ * array.
+ * - Tags listed in META_BC_SUBELEMENTS will have their values changed to
+ * array( '*' => $value ). This may be skipped by including 'nosub' in
+ * the value array.
+ * - If META_TYPE is 'BCarray', set it to 'default'
+ * - If META_TYPE is 'BCassoc', set it to 'default'
+ * - If META_TYPE is 'BCkvp', perform the transformation (even if
+ * the Types transformation is not being applied).
+ *
+ * Types: (assoc) Apply transformations based on META_TYPE. The values
+ * array is an associative array with the following possible keys:
+ * - AssocAsObject: (bool) If true, return arrays with META_TYPE 'assoc'
+ * as objects.
+ * - ArmorKVP: (string) If provided, transform arrays with META_TYPE 'kvp'
+ * and 'BCkvp' into arrays of two-element arrays, something like this:
+ * $output = array();
+ * foreach ( $input as $key => $value ) {
+ * $pair = array();
+ * $pair[$META_KVP_KEY_NAME ?: $ArmorKVP_value] = $key;
+ * ApiResult::setContentValue( $pair, 'value', $value );
+ * $output[] = $pair;
+ * }
+ *
+ * Strip: (string) Strips metadata keys from the result.
+ * - 'all': Strip all metadata, recursively
+ * - 'base': Strip metadata at the top-level only.
+ * - 'none': Do not strip metadata.
+ * - 'bc': Like 'all', but leave certain pre-1.25 keys.
+ *
+ * @since 1.25
+ * @param array|string|null $path Path to fetch, see ApiResult::addValue
+ * @param array $transforms See above
+ * @return mixed Result data, or null if not found
+ */
+ public function getResultData( $path = array(), $transforms = array() ) {
+ $path = (array)$path;
+ if ( !$path ) {
+ return self::applyTransformations( $this->data, $transforms );
+ }
+
+ $last = array_pop( $path );
+ $ret = &$this->path( $path, 'dummy' );
+ if ( !isset( $ret[$last] ) ) {
+ return null;
+ } elseif ( is_array( $ret[$last] ) ) {
+ return self::applyTransformations( $ret[$last], $transforms );
+ } else {
+ return $ret[$last];
+ }
+ }
+
+ /**
+ * Get the size of the result, i.e. the amount of bytes in it
+ * @return int
+ */
+ public function getSize() {
+ return $this->size;
}
/**
* 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 string $name Index of $arr to add $value at
+ *
+ * @since 1.25
+ * @param array &$arr To add $value to
+ * @param string|int|null $name Index of $arr to add $value at,
+ * or null to use the next numeric index.
* @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 )
- ) {
- ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
+ if ( !( $flags & ApiResult::NO_VALIDATE ) ) {
+ $value = self::validateValue( $value );
+ }
+
+ if ( $name === null ) {
+ if ( $flags & ApiResult::ADD_ON_TOP ) {
+ array_unshift( $arr, $value );
+ } else {
+ array_push( $arr, $value );
+ }
+ return;
}
$exists = isset( $arr[$name] );
@@ -193,265 +297,968 @@ class ApiResult extends ApiBase {
$arr[$name] = $value;
}
} elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
- $merged = array_intersect_key( $arr[$name], $value );
- if ( !count( $merged ) ) {
+ $conflicts = array_intersect_key( $arr[$name], $value );
+ if ( !$conflicts ) {
$arr[$name] += $value;
} else {
- ApiBase::dieDebug( __METHOD__, "Attempting to merge element $name" );
+ $keys = join( ', ', array_keys( $conflicts ) );
+ throw new RuntimeException( "Conflicting keys ($keys) when attempting to merge element $name" );
}
} else {
- ApiBase::dieDebug(
- __METHOD__,
- "Attempting to add element $name=$value, existing value is {$arr[$name]}"
- );
+ throw new RuntimeException( "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
+ * Validate a value for addition to the result
* @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.
*/
- public static function setContent( &$arr, $value, $subElemName = null ) {
+ private static function validateValue( $value ) {
+ global $wgContLang;
+
+ if ( is_object( $value ) ) {
+ // Note we use is_callable() here instead of instanceof because
+ // ApiSerializable is an informal protocol (see docs there for details).
+ if ( is_callable( array( $value, 'serializeForApiResult' ) ) ) {
+ $oldValue = $value;
+ $value = $value->serializeForApiResult();
+ if ( is_object( $value ) ) {
+ throw new UnexpectedValueException(
+ get_class( $oldValue ) . "::serializeForApiResult() returned an object of class " .
+ get_class( $value )
+ );
+ }
+
+ // Recursive call instead of fall-through so we can throw a
+ // better exception message.
+ try {
+ return self::validateValue( $value );
+ } catch ( Exception $ex ) {
+ throw new UnexpectedValueException(
+ get_class( $oldValue ) . "::serializeForApiResult() returned an invalid value: " .
+ $ex->getMessage(),
+ 0,
+ $ex
+ );
+ }
+ } elseif ( is_callable( array( $value, '__toString' ) ) ) {
+ $value = (string)$value;
+ } else {
+ $value = (array)$value + array( self::META_TYPE => 'assoc' );
+ }
+ }
if ( is_array( $value ) ) {
- ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ foreach ( $value as $k => $v ) {
+ $value[$k] = self::validateValue( $v );
+ }
+ } elseif ( is_float( $value ) && !is_finite( $value ) ) {
+ throw new InvalidArgumentException( "Cannot add non-finite floats to ApiResult" );
+ } elseif ( is_string( $value ) ) {
+ $value = $wgContLang->normalize( $value );
+ } elseif ( $value !== null && !is_scalar( $value ) ) {
+ $type = gettype( $value );
+ if ( is_resource( $value ) ) {
+ $type .= '(' . get_resource_type( $value ) . ')';
+ }
+ throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
}
- if ( is_null( $subElemName ) ) {
- ApiResult::setElement( $arr, '*', $value );
- } else {
- if ( !isset( $arr[$subElemName] ) ) {
- $arr[$subElemName] = array();
+
+ return $value;
+ }
+
+ /**
+ * Add value to the output data at the given path.
+ *
+ * Path can be an indexed array, each element specifying the branch at which to add the new
+ * value. Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value.
+ * If $path is null, the value will be inserted at the data root.
+ *
+ * @param array|string|int|null $path
+ * @param string|int|null $name See ApiResult::setValue()
+ * @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 ) {
+ $arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
+
+ if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
+ $newsize = $this->size + self::valueSize( $value );
+ if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
+ /// @todo Add i18n message when replacing calls to ->setWarning()
+ $msg = new ApiRawMessage( 'This result was truncated because it would otherwise ' .
+ ' be larger than the limit of $1 bytes', 'truncatedresult' );
+ $msg->numParams( $this->maxSize );
+ $this->errorFormatter->addWarning( 'result', $msg );
+ return false;
}
- ApiResult::setElement( $arr[$subElemName], '*', $value );
+ $this->size = $newsize;
}
+
+ self::setValue( $arr, $name, $value, $flags );
+ return true;
}
/**
- * 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
+ * Remove an output value to the array by name.
+ * @param array &$arr To remove $value from
+ * @param string|int $name Index of $arr to remove
+ * @return mixed Old value, or null
*/
- public function setSubelements( &$arr, $names ) {
- // In raw mode, add the '_subelements', otherwise just ignore
- if ( !$this->getIsRawMode() ) {
- return;
+ public static function unsetValue( array &$arr, $name ) {
+ $ret = null;
+ if ( isset( $arr[$name] ) ) {
+ $ret = $arr[$name];
+ unset( $arr[$name] );
}
- if ( $arr === null || $names === null || !is_array( $arr ) ) {
- ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ return $ret;
+ }
+
+ /**
+ * Remove value from the output data at the given path.
+ *
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param string|int|null $name Index to remove at $path.
+ * If null, $path itself is removed.
+ * @param int $flags Flags used when adding the value
+ * @return mixed Old value, or null
+ */
+ public function removeValue( $path, $name, $flags = 0 ) {
+ $path = (array)$path;
+ if ( $name === null ) {
+ if ( !$path ) {
+ throw new InvalidArgumentException( 'Cannot remove the data root' );
+ }
+ $name = array_pop( $path );
}
- if ( !is_array( $names ) ) {
- $names = array( $names );
+ $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
+ if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
+ $newsize = $this->size - self::valueSize( $ret );
+ $this->size = max( $newsize, 0 );
}
- if ( !isset( $arr['_subelements'] ) ) {
- $arr['_subelements'] = $names;
+ return $ret;
+ }
+
+ /**
+ * Add an output value to the array by name and mark as META_CONTENT.
+ *
+ * @since 1.25
+ * @param array &$arr To add $value to
+ * @param string|int $name Index of $arr to add $value at.
+ * @param mixed $value
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+ */
+ public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
+ if ( $name === null ) {
+ throw new InvalidArgumentException( 'Content value must be named' );
+ }
+ self::setContentField( $arr, $name, $flags );
+ self::setValue( $arr, $name, $value, $flags );
+ }
+
+ /**
+ * Add value to the output data at the given path and mark as META_CONTENT
+ *
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param string|int $name See ApiResult::setValue()
+ * @param mixed $value
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+ * @return bool True if $value fits in the result, false if not
+ */
+ public function addContentValue( $path, $name, $value, $flags = 0 ) {
+ if ( $name === null ) {
+ throw new InvalidArgumentException( 'Content value must be named' );
+ }
+ $this->addContentField( $path, $name, $flags );
+ $this->addValue( $path, $name, $value, $flags );
+ }
+
+ /**
+ * Add the numeric limit for a limit=max to the result.
+ *
+ * @since 1.25
+ * @param string $moduleName
+ * @param int $limit
+ */
+ public function addParsedLimit( $moduleName, $limit ) {
+ // Add value, allowing overwriting
+ $this->addValue( 'limits', $moduleName, $limit,
+ ApiResult::OVERRIDE | ApiResult::NO_SIZE_CHECK );
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Metadata
+ * @{
+ */
+
+ /**
+ * Set the name of the content field name (META_CONTENT)
+ *
+ * @since 1.25
+ * @param array &$arr
+ * @param string|int $name Name of the field
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+ */
+ public static function setContentField( array &$arr, $name, $flags = 0 ) {
+ if ( isset( $arr[self::META_CONTENT] ) &&
+ isset( $arr[$arr[self::META_CONTENT]] ) &&
+ !( $flags & self::OVERRIDE )
+ ) {
+ throw new RuntimeException(
+ "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
+ " is already set as the content element"
+ );
+ }
+ $arr[self::META_CONTENT] = $name;
+ }
+
+ /**
+ * Set the name of the content field name (META_CONTENT)
+ *
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param string|int $name Name of the field
+ * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
+ */
+ public function addContentField( $path, $name, $flags = 0 ) {
+ $arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
+ self::setContentField( $arr, $name, $flags );
+ }
+
+ /**
+ * Causes the elements with the specified names to be output as
+ * subelements rather than attributes.
+ * @since 1.25 is static
+ * @param array &$arr
+ * @param array|string|int $names The element name(s) to be output as subelements
+ */
+ public static function setSubelementsList( array &$arr, $names ) {
+ if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
+ $arr[self::META_SUBELEMENTS] = (array)$names;
} else {
- $arr['_subelements'] = array_merge( $arr['_subelements'], $names );
+ $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$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 array $arr
- * @param string $tag Tag name
+ * Causes the elements with the specified names to be output as
+ * subelements rather than attributes.
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param array|string|int $names The element name(s) to be output as subelements
*/
- public function setIndexedTagName( &$arr, $tag ) {
- // In raw mode, add the '_element', otherwise just ignore
- if ( !$this->getIsRawMode() ) {
- return;
+ public function addSubelementsList( $path, $names ) {
+ $arr = &$this->path( $path );
+ self::setSubelementsList( $arr, $names );
+ }
+
+ /**
+ * Causes the elements with the specified names to be output as
+ * attributes (when possible) rather than as subelements.
+ * @since 1.25
+ * @param array &$arr
+ * @param array|string|int $names The element name(s) to not be output as subelements
+ */
+ public static function unsetSubelementsList( array &$arr, $names ) {
+ if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
+ $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
}
- if ( $arr === null || $tag === null || !is_array( $arr ) || is_array( $tag ) ) {
- ApiBase::dieDebug( __METHOD__, 'Bad parameter' );
+ }
+
+ /**
+ * Causes the elements with the specified names to be output as
+ * attributes (when possible) rather than as subelements.
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param array|string|int $names The element name(s) to not be output as subelements
+ */
+ public function removeSubelementsList( $path, $names ) {
+ $arr = &$this->path( $path );
+ self::unsetSubelementsList( $arr, $names );
+ }
+
+ /**
+ * Set the tag name for numeric-keyed values in XML format
+ * @since 1.25 is static
+ * @param array &$arr
+ * @param string $tag Tag name
+ */
+ public static function setIndexedTagName( array &$arr, $tag ) {
+ if ( !is_string( $tag ) ) {
+ throw new InvalidArgumentException( 'Bad tag name' );
}
- // Do not use setElement() as it is ok to call this more than once
- $arr['_element'] = $tag;
+ $arr[self::META_INDEXED_TAG_NAME] = $tag;
}
/**
- * Calls setIndexedTagName() on each sub-array of $arr
- * @param array $arr
+ * Set the tag name for numeric-keyed values in XML format
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
* @param string $tag Tag name
*/
- public function setIndexedTagName_recursive( &$arr, $tag ) {
- if ( !is_array( $arr ) ) {
- return;
+ public function addIndexedTagName( $path, $tag ) {
+ $arr = &$this->path( $path );
+ self::setIndexedTagName( $arr, $tag );
+ }
+
+ /**
+ * Set indexed tag name on $arr and all subarrays
+ *
+ * @since 1.25
+ * @param array &$arr
+ * @param string $tag Tag name
+ */
+ public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
+ if ( !is_string( $tag ) ) {
+ throw new InvalidArgumentException( 'Bad tag name' );
}
- foreach ( $arr as &$a ) {
- if ( !is_array( $a ) ) {
- continue;
+ $arr[self::META_INDEXED_TAG_NAME] = $tag;
+ foreach ( $arr as $k => &$v ) {
+ if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
+ self::setIndexedTagNameRecursive( $v, $tag );
}
- $this->setIndexedTagName( $a, $tag );
- $this->setIndexedTagName_recursive( $a, $tag );
}
}
/**
- * Calls setIndexedTagName() on an array already in the result.
- * 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 string $tag
+ * Set indexed tag name on $path and all subarrays
+ *
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param string $tag Tag name
*/
- public function setIndexedTagName_internal( $path, $tag ) {
- $data = &$this->mData;
- foreach ( (array)$path as $p ) {
- if ( !isset( $data[$p] ) ) {
- $data[$p] = array();
- }
- $data = &$data[$p];
+ public function addIndexedTagNameRecursive( $path, $tag ) {
+ $arr = &$this->path( $path );
+ self::setIndexedTagNameRecursive( $arr, $tag );
+ }
+
+ /**
+ * Preserve specified keys.
+ *
+ * This prevents XML name mangling and preventing keys from being removed
+ * by self::stripMetadata().
+ *
+ * @since 1.25
+ * @param array &$arr
+ * @param array|string $names The element name(s) to preserve
+ */
+ public static function setPreserveKeysList( array &$arr, $names ) {
+ if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
+ $arr[self::META_PRESERVE_KEYS] = (array)$names;
+ } else {
+ $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
}
- if ( is_null( $data ) ) {
- return;
+ }
+
+ /**
+ * Preserve specified keys.
+ * @since 1.25
+ * @see self::setPreserveKeysList()
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param array|string $names The element name(s) to preserve
+ */
+ public function addPreserveKeysList( $path, $names ) {
+ $arr = &$this->path( $path );
+ self::setPreserveKeysList( $arr, $names );
+ }
+
+ /**
+ * Don't preserve specified keys.
+ * @since 1.25
+ * @see self::setPreserveKeysList()
+ * @param array &$arr
+ * @param array|string $names The element name(s) to not preserve
+ */
+ public static function unsetPreserveKeysList( array &$arr, $names ) {
+ if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
+ $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
}
- $this->setIndexedTagName( $data, $tag );
}
/**
- * Add value to the output data at the given path.
- * Path can be an indexed array, each element specifying the branch at which to add the new
- * value. Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value.
- * 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.
+ * Don't preserve specified keys.
+ * @since 1.25
+ * @see self::setPreserveKeysList()
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param array|string $names The element name(s) to not preserve
+ */
+ public function removePreserveKeysList( $path, $names ) {
+ $arr = &$this->path( $path );
+ self::unsetPreserveKeysList( $arr, $names );
+ }
+
+ /**
+ * Set the array data type
*
- * @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.25
+ * @param array &$arr
+ * @param string $type See ApiResult::META_TYPE
+ * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ */
+ public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
+ if ( !in_array( $type, array( 'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp' ), true ) ) {
+ throw new InvalidArgumentException( 'Bad type' );
+ }
+ $arr[self::META_TYPE] = $type;
+ if ( is_string( $kvpKeyName ) ) {
+ $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
+ }
+ }
+
+ /**
+ * Set the array data type for a path
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param string $type See ApiResult::META_TYPE
+ * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ */
+ public function addArrayType( $path, $tag, $kvpKeyName = null ) {
+ $arr = &$this->path( $path );
+ self::setArrayType( $arr, $tag, $kvpKeyName );
+ }
+
+ /**
+ * Set the array data type recursively
+ * @since 1.25
+ * @param array &$arr
+ * @param string $type See ApiResult::META_TYPE
+ * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ */
+ public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
+ self::setArrayType( $arr, $type, $kvpKeyName );
+ foreach ( $arr as $k => &$v ) {
+ if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
+ self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
+ }
+ }
+ }
+
+ /**
+ * Set the array data type for a path recursively
+ * @since 1.25
+ * @param array|string|null $path See ApiResult::addValue()
+ * @param string $type See ApiResult::META_TYPE
+ * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
+ */
+ public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
+ $arr = &$this->path( $path );
+ self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
+ }
+
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Utility
+ * @{
+ */
+
+ /**
+ * Test whether a key should be considered metadata
*
- * @since 1.21 int $flags replaced boolean $override
+ * @param string $key
+ * @return bool
*/
- public function addValue( $path, $name, $value, $flags = 0 ) {
- $data = &$this->mData;
- if ( $this->mCheckingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
- $newsize = $this->mSize + self::size( $value );
- $maxResultSize = $this->getConfig()->get( 'APIMaxResultSize' );
- if ( $newsize > $maxResultSize ) {
- $this->setWarning(
- "This result was truncated because it would otherwise be larger than the " .
- "limit of {$maxResultSize} bytes" );
+ public static function isMetadataKey( $key ) {
+ return substr( $key, 0, 1 ) === '_';
+ }
- return false;
+ /**
+ * Apply transformations to an array, returning the transformed array.
+ *
+ * @see ApiResult::getResultData()
+ * @since 1.25
+ * @param array $data
+ * @param array $transforms
+ * @return array|object
+ */
+ protected static function applyTransformations( array $dataIn, array $transforms ) {
+ $strip = isset( $transforms['Strip'] ) ? $transforms['Strip'] : 'none';
+ if ( $strip === 'base' ) {
+ $transforms['Strip'] = 'none';
+ }
+ $transformTypes = isset( $transforms['Types'] ) ? $transforms['Types'] : null;
+ if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
+ throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
+ }
+
+ $metadata = array();
+ $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
+
+ if ( isset( $transforms['Custom'] ) ) {
+ if ( !is_callable( $transforms['Custom'] ) ) {
+ throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
}
- $this->mSize = $newsize;
+ call_user_func_array( $transforms['Custom'], array( &$data, &$metadata ) );
}
- $addOnTop = $flags & ApiResult::ADD_ON_TOP;
- if ( $path !== null ) {
- foreach ( (array)$path as $p ) {
- if ( !isset( $data[$p] ) ) {
- if ( $addOnTop ) {
- $data = array( $p => array() ) + $data;
- $addOnTop = false;
- } else {
- $data[$p] = array();
+ if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
+ isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
+ !isset( $metadata[self::META_KVP_KEY_NAME] )
+ ) {
+ throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
+ 'ApiResult::META_KVP_KEY_NAME metadata item' );
+ }
+
+ // BC transformations
+ $boolKeys = null;
+ $forceKVP = false;
+ if ( isset( $transforms['BC'] ) ) {
+ if ( !is_array( $transforms['BC'] ) ) {
+ throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
+ }
+ if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
+ $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
+ ? array_flip( $metadata[self::META_BC_BOOLS] )
+ : array();
+ }
+
+ if ( !in_array( 'no*', $transforms['BC'], true ) &&
+ isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
+ ) {
+ $k = $metadata[self::META_CONTENT];
+ $data['*'] = $data[$k];
+ unset( $data[$k] );
+ $metadata[self::META_CONTENT] = '*';
+ }
+
+ if ( !in_array( 'nosub', $transforms['BC'], true ) &&
+ isset( $metadata[self::META_BC_SUBELEMENTS] )
+ ) {
+ foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
+ if ( isset( $data[$k] ) ) {
+ $data[$k] = array(
+ '*' => $data[$k],
+ self::META_CONTENT => '*',
+ self::META_TYPE => 'assoc',
+ );
}
}
- $data = &$data[$p];
+ }
+
+ if ( isset( $metadata[self::META_TYPE] ) ) {
+ switch ( $metadata[self::META_TYPE] ) {
+ case 'BCarray':
+ case 'BCassoc':
+ $metadata[self::META_TYPE] = 'default';
+ break;
+ case 'BCkvp':
+ $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
+ break;
+ }
}
}
- if ( !$name ) {
- // Add list element
- if ( $addOnTop ) {
- // This element needs to be inserted in the beginning
- // Numerical indexes will be renumbered
- array_unshift( $data, $value );
- } else {
- // Add new value at the end
- $data[] = $value;
+ // Figure out type, do recursive calls, and do boolean transform if necessary
+ $defaultType = 'array';
+ $maxKey = -1;
+ foreach ( $data as $k => &$v ) {
+ $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
+ if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
+ if ( !$v ) {
+ unset( $data[$k] );
+ continue;
+ }
+ $v = '';
+ }
+ if ( is_string( $k ) ) {
+ $defaultType = 'assoc';
+ } elseif ( $k > $maxKey ) {
+ $maxKey = $k;
}
- } else {
- // Add named element
- self::setElement( $data, $name, $value, $flags );
}
+ unset( $v );
- return true;
+ // Determine which metadata to keep
+ switch ( $strip ) {
+ case 'all':
+ case 'base':
+ $keepMetadata = array();
+ break;
+ case 'none':
+ $keepMetadata = &$metadata;
+ break;
+ case 'bc':
+ $keepMetadata = array_intersect_key( $metadata, array(
+ self::META_INDEXED_TAG_NAME => 1,
+ self::META_SUBELEMENTS => 1,
+ ) );
+ break;
+ default:
+ throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
+ }
+
+ // Type transformation
+ if ( $transformTypes !== null ) {
+ if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
+ $defaultType = 'assoc';
+ }
+
+ // Override type, if provided
+ $type = $defaultType;
+ if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
+ $type = $metadata[self::META_TYPE];
+ }
+ if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
+ empty( $transformTypes['ArmorKVP'] )
+ ) {
+ $type = 'assoc';
+ } elseif ( $type === 'BCarray' ) {
+ $type = 'array';
+ } elseif ( $type === 'BCassoc' ) {
+ $type = 'assoc';
+ }
+
+ // Apply transformation
+ switch ( $type ) {
+ case 'assoc':
+ $metadata[self::META_TYPE] = 'assoc';
+ $data += $keepMetadata;
+ return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
+
+ case 'array':
+ ksort( $data );
+ $data = array_values( $data );
+ $metadata[self::META_TYPE] = 'array';
+ return $data + $keepMetadata;
+
+ case 'kvp':
+ case 'BCkvp':
+ $key = isset( $metadata[self::META_KVP_KEY_NAME] )
+ ? $metadata[self::META_KVP_KEY_NAME]
+ : $transformTypes['ArmorKVP'];
+ $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
+ $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
+
+ $ret = array();
+ foreach ( $data as $k => $v ) {
+ $item = array(
+ $key => $k,
+ $valKey => $v,
+ );
+ if ( $strip === 'none' ) {
+ $item += array(
+ self::META_PRESERVE_KEYS => array( $key ),
+ self::META_CONTENT => $valKey,
+ self::META_TYPE => 'assoc',
+ );
+ }
+ $ret[] = $assocAsObject ? (object)$item : $item;
+ }
+ $metadata[self::META_TYPE] = 'array';
+
+ return $ret + $keepMetadata;
+
+ default:
+ throw new UnexpectedValueException( "Unknown type '$type'" );
+ }
+ } else {
+ return $data + $keepMetadata;
+ }
}
/**
- * Add a parsed limit=max to the result.
+ * Recursively remove metadata keys from a data array or object
*
- * @param string $moduleName
- * @param int $limit
+ * Note this removes all potential metadata keys, not just the defined
+ * ones.
+ *
+ * @since 1.25
+ * @param array|object $data
+ * @return array|object
*/
- public function setParsedLimit( $moduleName, $limit ) {
- // Add value, allowing overwriting
- $this->addValue( 'limits', $moduleName, $limit, ApiResult::OVERRIDE );
+ public static function stripMetadata( $data ) {
+ if ( is_array( $data ) || is_object( $data ) ) {
+ $isObj = is_object( $data );
+ if ( $isObj ) {
+ $data = (array)$data;
+ }
+ $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
+ ? (array)$data[self::META_PRESERVE_KEYS]
+ : array();
+ foreach ( $data as $k => $v ) {
+ if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
+ unset( $data[$k] );
+ } elseif ( is_array( $v ) || is_object( $v ) ) {
+ $data[$k] = self::stripMetadata( $v );
+ }
+ }
+ if ( $isObj ) {
+ $data = (object)$data;
+ }
+ }
+ return $data;
}
/**
- * Unset a value previously added to the result set.
- * Fails silently if the value isn't found.
- * For parameters, see addValue()
- * @param array|null $path
- * @param string $name
+ * Remove metadata keys from a data array or object, non-recursive
+ *
+ * Note this removes all potential metadata keys, not just the defined
+ * ones.
+ *
+ * @since 1.25
+ * @param array|object $data
+ * @param array &$metadata Store metadata here, if provided
+ * @return array|object
*/
- public function unsetValue( $path, $name ) {
- $data = &$this->mData;
- if ( $path !== null ) {
- foreach ( (array)$path as $p ) {
- if ( !isset( $data[$p] ) ) {
- return;
+ public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
+ if ( !is_array( $metadata ) ) {
+ $metadata = array();
+ }
+ if ( is_array( $data ) || is_object( $data ) ) {
+ $isObj = is_object( $data );
+ if ( $isObj ) {
+ $data = (array)$data;
+ }
+ $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
+ ? (array)$data[self::META_PRESERVE_KEYS]
+ : array();
+ foreach ( $data as $k => $v ) {
+ if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
+ $metadata[$k] = $v;
+ unset( $data[$k] );
}
- $data = &$data[$p];
+ }
+ if ( $isObj ) {
+ $data = (object)$data;
}
}
- $this->mSize -= self::size( $data[$name] );
- unset( $data[$name] );
+ return $data;
}
/**
- * Ensure all values in this result are valid UTF-8.
+ * 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.
+ * @note Once the deprecated public self::size is removed, we can rename this back to a less awkward name.
+ * @param mixed $value
+ * @return int
*/
- public function cleanUpUTF8() {
- array_walk_recursive( $this->mData, array( 'ApiResult', 'cleanUp_helper' ) );
+ private static function valueSize( $value ) {
+ $s = 0;
+ if ( is_array( $value ) ||
+ is_object( $value ) && !is_callable( array( $value, '__toString' ) )
+ ) {
+ foreach ( $value as $k => $v ) {
+ if ( !self::isMetadataKey( $s ) ) {
+ $s += self::valueSize( $v );
+ }
+ }
+ } elseif ( is_scalar( $value ) ) {
+ $s = strlen( $value );
+ }
+
+ return $s;
}
/**
- * Callback function for cleanUpUTF8()
+ * Return a reference to the internal data at $path
*
- * @param string $s
+ * @param array|string|null $path
+ * @param string $create
+ * If 'append', append empty arrays.
+ * If 'prepend', prepend empty arrays.
+ * If 'dummy', return a dummy array.
+ * Else, raise an error.
+ * @return array
*/
- private static function cleanUp_helper( &$s ) {
- if ( !is_string( $s ) ) {
- return;
+ private function &path( $path, $create = 'append' ) {
+ $path = (array)$path;
+ $ret = &$this->data;
+ foreach ( $path as $i => $k ) {
+ if ( !isset( $ret[$k] ) ) {
+ switch ( $create ) {
+ case 'append':
+ $ret[$k] = array();
+ break;
+ case 'prepend':
+ $ret = array( $k => array() ) + $ret;
+ break;
+ case 'dummy':
+ $tmp = array();
+ return $tmp;
+ default:
+ $fail = join( '.', array_slice( $path, 0, $i + 1 ) );
+ throw new InvalidArgumentException( "Path $fail does not exist" );
+ }
+ }
+ if ( !is_array( $ret[$k] ) ) {
+ $fail = join( '.', array_slice( $path, 0, $i + 1 ) );
+ throw new InvalidArgumentException( "Path $fail is not an array" );
+ }
+ $ret = &$ret[$k];
}
- global $wgContLang;
- $s = $wgContLang->normalize( $s );
+ return $ret;
}
+ /**@}*/
+
+ /************************************************************************//**
+ * @name Deprecated
+ * @{
+ */
+
/**
- * Converts a Status object to an array suitable for addValue
- * @param Status $status
- * @param string $errorType
+ * Call this function when special elements such as '_element'
+ * are needed by the formatter, for example in XML printing.
+ * @deprecated since 1.25, you shouldn't have been using it in the first place
+ * @since 1.23 $flag parameter added
+ * @param bool $flag Set the raw mode flag to this state
+ */
+ public function setRawMode( $flag = true ) {
+ // Can't wfDeprecated() here, since we need to set this flag from
+ // ApiMain for BC with stuff using self::getIsRawMode as
+ // "self::getIsXMLMode".
+ $this->isRawMode = $flag;
+ }
+
+ /**
+ * Returns true whether the formatter requested raw data.
+ * @deprecated since 1.25, you shouldn't have been using it in the first place
+ * @return bool
+ */
+ public function getIsRawMode() {
+ /// @todo: After Wikibase stops calling this, warn
+ return $this->isRawMode;
+ }
+
+ /**
+ * Get the result's internal data array (read-only)
+ * @deprecated since 1.25, use $this->getResultData() instead
* @return array
*/
- public function convertStatusToArray( $status, $errorType = 'error' ) {
- if ( $status->isGood() ) {
- return array();
+ public function getData() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return $this->getResultData( null, array(
+ 'BC' => array(),
+ 'Types' => array(),
+ 'Strip' => $this->isRawMode ? 'bc' : 'all',
+ ) );
+ }
+
+ /**
+ * 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() {
+ wfDeprecated( __METHOD__, '1.24' );
+ $this->checkingSize = false;
+ }
+
+ /**
+ * Re-enable size checking in addValue()
+ * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
+ */
+ public function enableSizeCheck() {
+ wfDeprecated( __METHOD__, '1.24' );
+ $this->checkingSize = true;
+ }
+
+ /**
+ * Alias for self::setValue()
+ *
+ * @since 1.21 int $flags replaced boolean $override
+ * @deprecated since 1.25, use self::setValue() instead
+ * @param array $arr To add $value to
+ * @param string $name Index of $arr to add $value at
+ * @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.
+ */
+ public static function setElement( &$arr, $name, $value, $flags = 0 ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ return self::setValue( $arr, $name, $value, $flags );
+ }
+
+ /**
+ * Adds a content element to an array.
+ * Use this function instead of hardcoding the '*' element.
+ * @deprecated since 1.25, use self::setContentValue() instead
+ * @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.
+ */
+ public static function setContent( &$arr, $value, $subElemName = null ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ if ( is_array( $value ) ) {
+ throw new InvalidArgumentException( __METHOD__ . ': Bad parameter' );
}
+ if ( is_null( $subElemName ) ) {
+ self::setContentValue( $arr, 'content', $value );
+ } else {
+ if ( !isset( $arr[$subElemName] ) ) {
+ $arr[$subElemName] = array();
+ }
+ self::setContentValue( $arr[$subElemName], 'content', $value );
+ }
+ }
- $result = array();
- foreach ( $status->getErrorsByType( $errorType ) as $error ) {
- $this->setIndexedTagName( $error['params'], 'param' );
- $result[] = $error;
+ /**
+ * Set indexed tag name on all subarrays of $arr
+ *
+ * Does not set the tag name for $arr itself.
+ *
+ * @deprecated since 1.25, use self::setIndexedTagNameRecursive() instead
+ * @param array $arr
+ * @param string $tag Tag name
+ */
+ public function setIndexedTagName_recursive( &$arr, $tag ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ if ( !is_array( $arr ) ) {
+ return;
}
- $this->setIndexedTagName( $result, $errorType );
+ if ( !is_string( $tag ) ) {
+ throw new InvalidArgumentException( 'Bad tag name' );
+ }
+ foreach ( $arr as $k => &$v ) {
+ if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
+ $v[self::META_INDEXED_TAG_NAME] = $tag;
+ $this->setIndexedTagName_recursive( $v, $tag );
+ }
+ }
+ }
- return $result;
+ /**
+ * Alias for self::addIndexedTagName()
+ * @deprecated since 1.25, use $this->addIndexedTagName() instead
+ * @param array $path Path to the array, like addValue()'s $path
+ * @param string $tag
+ */
+ public function setIndexedTagName_internal( $path, $tag ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ $this->addIndexedTagName( $path, $tag );
+ }
+
+ /**
+ * Alias for self::addParsedLimit()
+ * @deprecated since 1.25, use $this->addParsedLimit() instead
+ * @param string $moduleName
+ * @param int $limit
+ */
+ public function setParsedLimit( $moduleName, $limit ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ $this->addParsedLimit( $moduleName, $limit );
}
- public function execute() {
- ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
+ /**
+ * Set the ApiMain for use by $this->beginContinuation()
+ * @since 1.25
+ * @deprecated for backwards compatibility only, do not use
+ * @param ApiMain $main
+ */
+ public function setMainForContinuation( ApiMain $main ) {
+ $this->mainForContinuation = $main;
}
/**
@@ -460,172 +1267,148 @@ class ApiResult extends ApiBase {
* 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.
+ * @deprecated since 1.25, use ApiContinuationManager instead
+ * @param string|null $continue
+ * @param ApiBase[] $allModules
+ * @param array $generatedModules
+ * @return array
*/
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;
- }
+ wfDeprecated( __METHOD__, '1.25' );
+ if ( $this->mainForContinuation->getContinuationManager() ) {
+ throw new UnexpectedValueException(
+ __METHOD__ . ': Continuation already in progress from ' .
+ $this->mainForContinuation->getContinuationManager()->getSource()
+ );
+ }
+
+ // Ugh. If $continue doesn't match that in the request, temporarily
+ // replace the request when creating the ApiContinuationManager.
+ if ( $continue === null ) {
+ $continue = '';
}
+ if ( $this->mainForContinuation->getVal( 'continue', '' ) !== $continue ) {
+ $oldCtx = $this->mainForContinuation->getContext();
+ $newCtx = new DerivativeContext( $oldCtx );
+ $newCtx->setRequest( new DerivativeRequest(
+ $oldCtx->getRequest(),
+ array( 'continue' => $continue ) + $oldCtx->getRequest()->getValues(),
+ $oldCtx->getRequest()->wasPosted()
+ ) );
+ $this->mainForContinuation->setContext( $newCtx );
+ $reset = new ScopedCallback(
+ array( $this->mainForContinuation, 'setContext' ),
+ array( $oldCtx )
+ );
+ }
+ $manager = new ApiContinuationManager(
+ $this->mainForContinuation, $allModules, $generatedModules
+ );
+ $reset = null;
+
+ $this->mainForContinuation->setContinuationManager( $manager );
return array(
- $this->generatorDone,
- $runModules,
+ $manager->isGeneratorDone(),
+ $manager->getRunModules(),
);
}
/**
- * Set the continuation parameter for a module
- *
* @since 1.24
+ * @deprecated since 1.25, use ApiContinuationManager instead
* @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'
+ wfDeprecated( __METHOD__, '1.25' );
+ if ( $this->mainForContinuation->getContinuationManager() ) {
+ $this->mainForContinuation->getContinuationManager()->addContinueParam(
+ $module, $paramName, $paramValue
);
}
- $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
+ * @deprecated since 1.25, use ApiContinuationManager instead
* @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 );
+ wfDeprecated( __METHOD__, '1.25' );
+ if ( $this->mainForContinuation->getContinuationManager() ) {
+ $this->mainForContinuation->getContinuationManager()->addGeneratorContinueParam(
+ $module, $paramName, $paramValue
+ );
}
- $this->generatorContinuationData[$name][$paramName] = $paramValue;
}
/**
* Close continuation, writing the data into the result
- *
* @since 1.24
+ * @deprecated since 1.25, use ApiContinuationManager instead
* @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' ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ if ( !$this->mainForContinuation->getContinuationManager() ) {
+ return;
+ }
+
if ( $style === 'raw' ) {
- $key = 'query-continue';
- $data = array_merge_recursive(
- $this->continuationData, $this->generatorContinuationData
- );
+ $data = $this->mainForContinuation->getContinuationManager()->getRawContinuation();
+ if ( $data ) {
+ $this->addValue( null, 'query-continue', $data, self::ADD_ON_TOP | self::NO_SIZE_CHECK );
+ }
} else {
- $key = 'continue';
- $data = array();
-
- $finishedModules = array_diff(
- array_keys( $this->continueAllModules ),
- array_keys( $this->continuationData )
- );
+ $this->mainForContinuation->getContinuationManager()->setContinuationIntoResult( $this );
+ }
+ }
- // First, grab the non-generator-using continuation data
- $continuationData = array_diff_key(
- $this->continuationData, $this->continueGeneratedModules
- );
- foreach ( $continuationData as $module => $kvp ) {
- $data += $kvp;
- }
+ /**
+ * No-op, this is now checked on insert.
+ * @deprecated since 1.25
+ */
+ public function cleanUpUTF8() {
+ wfDeprecated( __METHOD__, '1.25' );
+ }
- // 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;
- }
+ /**
+ * 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.
+ * @deprecated since 1.25, no external users known and there doesn't seem
+ * to be any case for such use over just checking the return value from the
+ * add/set methods.
+ * @param mixed $value
+ * @return int
+ */
+ public static function size( $value ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ return self::valueSize( $value );
+ }
- // 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 );
- }
+ /**
+ * Converts a Status object to an array suitable for addValue
+ * @deprecated since 1.25, use ApiErrorFormatter::arrayFromStatus()
+ * @param Status $status
+ * @param string $errorType
+ * @return array
+ */
+ public function convertStatusToArray( $status, $errorType = 'error' ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ return $this->errorFormatter->arrayFromStatus( $status, $errorType );
}
+
+ /**@}*/
}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */
diff --git a/includes/api/ApiRevisionDelete.php b/includes/api/ApiRevisionDelete.php
index cbc30704..28962316 100644
--- a/includes/api/ApiRevisionDelete.php
+++ b/includes/api/ApiRevisionDelete.php
@@ -111,7 +111,7 @@ class ApiRevisionDelete extends ApiBase {
// @codingStandardsIgnoreEnd
$data['items'] = array_values( $data['items'] );
- $result->setIndexedTagName( $data['items'], 'i' );
+ ApiResult::setIndexedTagName( $data['items'], 'i' );
$result->addValue( null, $this->getModuleName(), $data );
}
@@ -121,12 +121,12 @@ class ApiRevisionDelete extends ApiBase {
);
$errors = $this->formatStatusMessages( $status->getErrorsByType( 'error' ) );
if ( $errors ) {
- $this->getResult()->setIndexedTagName( $errors, 'e' );
+ ApiResult::setIndexedTagName( $errors, 'e' );
$ret['errors'] = $errors;
}
$warnings = $this->formatStatusMessages( $status->getErrorsByType( 'warning' ) );
if ( $warnings ) {
- $this->getResult()->setIndexedTagName( $warnings, 'w' );
+ ApiResult::setIndexedTagName( $warnings, 'w' );
$ret['warnings'] = $warnings;
}
@@ -146,14 +146,14 @@ class ApiRevisionDelete extends ApiBase {
$message = array( 'message' => $msg->getKey() );
if ( $msg->getParams() ) {
$message['params'] = $msg->getParams();
- $result->setIndexedTagName( $message['params'], 'p' );
+ ApiResult::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' );
+ ApiResult::setIndexedTagName( $message['params'], 'p' );
$msg->params( $m['params'] );
}
}
@@ -199,34 +199,18 @@ class ApiRevisionDelete extends ApiBase {
);
}
- 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() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=revisiondelete&target=Main%20Page&type=revision&ids=12345&' .
+ '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&' .
+ => 'apihelp-revisiondelete-example-revision',
+ '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"',
+ => 'apihelp-revisiondelete-example-log',
);
}
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index f4d3c541..02e62a03 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -120,31 +120,10 @@ class ApiRollback extends ApiBase {
'nochange'
),
),
- );
- }
-
- public function getParamDescription() {
- $p = $this->getModulePrefix();
-
- return array(
- '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.'
+ // Standard definition automatically inserted
+ ApiBase::PARAM_HELP_MSG_APPEND => array( 'api-help-param-token-webui' ),
),
- '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 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.'
);
}
@@ -211,12 +190,13 @@ class ApiRollback extends ApiBase {
return $this->mTitleObj;
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=rollback&title=Main%20Page&user=Catrope&token=123ABC',
- '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'
+ 'action=rollback&title=Main%20Page&user=Example&token=123ABC' =>
+ 'apihelp-rollback-example-simple',
+ 'action=rollback&title=Main%20Page&user=192.0.2.5&' .
+ 'token=123ABC&summary=Reverting%20vandalism&markbot=1' =>
+ 'apihelp-rollback-example-summary',
);
}
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
index a2771a0c..d4661125 100644
--- a/includes/api/ApiRsd.php
+++ b/includes/api/ApiRsd.php
@@ -37,12 +37,15 @@ class ApiRsd extends ApiBase {
$result->addValue( null, 'version', '1.0' );
$result->addValue( null, 'xmlns', 'http://archipelago.phrasewise.com/rsd' );
- $service = array( 'apis' => $this->formatRsdApiList() );
- ApiResult::setContent( $service, 'MediaWiki', 'engineName' );
- ApiResult::setContent( $service, 'https://www.mediawiki.org/', 'engineLink' );
- ApiResult::setContent( $service, Title::newMainPage()->getCanonicalURL(), 'homePageLink' );
+ $service = array(
+ 'apis' => $this->formatRsdApiList(),
+ 'engineName' => 'MediaWiki',
+ 'engineLink' => 'https://www.mediawiki.org/',
+ 'homePageLink' => Title::newMainPage()->getCanonicalURL(),
+ );
- $result->setIndexedTagName( $service['apis'], 'api' );
+ ApiResult::setSubelementsList( $service, array( 'engineName', 'engineLink', 'homePageLink' ) );
+ ApiResult::setIndexedTagName( $service['apis'], 'api' );
$result->addValue( null, 'service', $service );
}
@@ -51,21 +54,10 @@ class ApiRsd extends ApiBase {
return new ApiFormatXmlRsd( $this->getMain(), 'xml' );
}
- public function getAllowedParams() {
- return array();
- }
-
- public function getParamDescription() {
- return array();
- }
-
- public function getDescription() {
- return 'Export an RSD (Really Simple Discovery) schema.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=rsd'
+ 'action=rsd'
+ => 'apihelp-rsd-example-simple',
);
}
@@ -110,7 +102,7 @@ class ApiRsd extends ApiBase {
)
),
);
- wfRunHooks( 'ApiRsdServiceApis', array( &$apis ) );
+ Hooks::run( 'ApiRsdServiceApis', array( &$apis ) );
return $apis;
}
@@ -134,7 +126,8 @@ class ApiRsd extends ApiBase {
);
$settings = array();
if ( isset( $info['docs'] ) ) {
- ApiResult::setContent( $settings, $info['docs'], 'docs' );
+ $settings['docs'] = $info['docs'];
+ ApiResult::setSubelementsList( $settings, 'docs' );
}
if ( isset( $info['settings'] ) ) {
foreach ( $info['settings'] as $setting => $val ) {
@@ -144,12 +137,12 @@ class ApiRsd extends ApiBase {
$xmlVal = $val;
}
$setting = array( 'name' => $setting );
- ApiResult::setContent( $setting, $xmlVal );
+ ApiResult::setContentValue( $setting, 'value', $xmlVal );
$settings[] = $setting;
}
}
if ( count( $settings ) ) {
- $this->getResult()->setIndexedTagName( $settings, 'setting' );
+ ApiResult::setIndexedTagName( $settings, 'setting' );
$data['settings'] = $settings;
}
$outputData[] = $data;
@@ -168,4 +161,9 @@ class ApiFormatXmlRsd extends ApiFormatXml {
public function getMimeType() {
return 'application/rsd+xml';
}
+
+ public static function recXmlPrint( $name, $value, $indent, $attributes = array() ) {
+ unset( $attributes['_idx'] );
+ return parent::recXmlPrint( $name, $value, $indent, $attributes );
+ }
}
diff --git a/includes/api/ApiSerializable.php b/includes/api/ApiSerializable.php
new file mode 100644
index 00000000..70e93a6c
--- /dev/null
+++ b/includes/api/ApiSerializable.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Created on Feb 25, 2015
+ *
+ * Copyright © 2015 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
+ */
+
+/**
+ * This interface allows for overriding the default conversion applied by
+ * ApiResult::validateValue().
+ *
+ * @note This is currently an informal interface; it need not be explicitly
+ * implemented, as long as the method is provided. This allows for extension
+ * code to maintain compatibility with older MediaWiki while still taking
+ * advantage of this where it exists.
+ *
+ * @ingroup API
+ * @since 1.25
+ */
+interface ApiSerializable {
+ /**
+ * Return the value to be added to ApiResult in place of this object.
+ *
+ * The returned value must not be an object, and must pass
+ * all checks done by ApiResult::validateValue().
+ *
+ * @return mixed
+ */
+ public function serializeForApiResult();
+}
diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php
index 5d527fc7..86a3f6aa 100644
--- a/includes/api/ApiSetNotificationTimestamp.php
+++ b/includes/api/ApiSetNotificationTimestamp.php
@@ -46,7 +46,8 @@ class ApiSetNotificationTimestamp extends ApiBase {
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );
- $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+ $continuationManager = new ApiContinuationManager( $this, array(), array() );
+ $this->setContinuationManager( $continuationManager );
$pageSet = $this->getPageSet();
if ( $params['entirewatchlist'] && $pageSet->getDataSource() !== null ) {
@@ -73,7 +74,8 @@ class ApiSetNotificationTimestamp extends ApiBase {
}
$title = reset( $pageSet->getGoodTitles() );
if ( $title ) {
- $timestamp = Revision::getTimestampFromId( $title, $params['torevid'] );
+ $timestamp = Revision::getTimestampFromId(
+ $title, $params['torevid'], Revision::READ_LATEST );
if ( $timestamp ) {
$timestamp = $dbw->timestamp( $timestamp );
} else {
@@ -86,7 +88,8 @@ class ApiSetNotificationTimestamp extends ApiBase {
}
$title = reset( $pageSet->getGoodTitles() );
if ( $title ) {
- $revid = $title->getNextRevisionID( $params['newerthanrevid'] );
+ $revid = $title->getNextRevisionID(
+ $params['newerthanrevid'], Title::GAID_FOR_UPDATE );
if ( $revid ) {
$timestamp = $dbw->timestamp( Revision::getTimestampFromId( $title, $revid ) );
} else {
@@ -112,21 +115,21 @@ class ApiSetNotificationTimestamp extends ApiBase {
foreach ( $pageSet->getInvalidTitles() as $title ) {
$r = array();
$r['title'] = $title;
- $r['invalid'] = '';
+ $r['invalid'] = true;
$result[] = $r;
}
foreach ( $pageSet->getMissingPageIDs() as $p ) {
$page = array();
$page['pageid'] = $p;
- $page['missing'] = '';
- $page['notwatched'] = '';
+ $page['missing'] = true;
+ $page['notwatched'] = true;
$result[] = $page;
}
foreach ( $pageSet->getMissingRevisionIDs() as $r ) {
$rev = array();
$rev['revid'] = $r;
- $rev['missing'] = '';
- $rev['notwatched'] = '';
+ $rev['missing'] = true;
+ $rev['notwatched'] = true;
$result[] = $rev;
}
@@ -160,7 +163,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
'title' => $title->getPrefixedText(),
);
if ( !$title->exists() ) {
- $r['missing'] = '';
+ $r['missing'] = true;
}
if ( isset( $timestamps[$ns] ) && array_key_exists( $dbkey, $timestamps[$ns] ) ) {
$r['notificationtimestamp'] = '';
@@ -168,17 +171,18 @@ class ApiSetNotificationTimestamp extends ApiBase {
$r['notificationtimestamp'] = wfTimestamp( TS_ISO_8601, $timestamps[$ns][$dbkey] );
}
} else {
- $r['notwatched'] = '';
+ $r['notwatched'] = true;
}
$result[] = $r;
}
}
- $apiResult->setIndexedTagName( $result, 'page' );
+ ApiResult::setIndexedTagName( $result, 'page' );
}
$apiResult->addValue( null, $this->getModuleName(), $result );
- $apiResult->endContinuation();
+ $this->setContinuationManager( null );
+ $continuationManager->setContinuationIntoResult( $apiResult );
}
/**
@@ -219,7 +223,9 @@ class ApiSetNotificationTimestamp extends ApiBase {
'newerthanrevid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
- 'continue' => '',
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
@@ -228,34 +234,17 @@ class ApiSetNotificationTimestamp extends ApiBase {
return $result;
}
- public function getParamDescription() {
- return $this->getPageSet()->getFinalParamDescription() + array(
- 'entirewatchlist' => 'Work on all watched pages',
- '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)',
- 'continue' => 'When more results are available, use this to continue',
- );
- }
-
- public function getDescription() {
- return array( 'Update the notification timestamp for watched pages.',
- 'This affects the highlighting of changed pages in the watchlist and history,',
- 'and the sending of email when the "Email me when a page on my watchlist is',
- 'changed" preference is enabled.'
- );
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
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&' .
+ 'action=setnotificationtimestamp&entirewatchlist=&token=123ABC'
+ => 'apihelp-setnotificationtimestamp-example-all',
+ 'action=setnotificationtimestamp&titles=Main_page&token=123ABC'
+ => 'apihelp-setnotificationtimestamp-example-page',
+ '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',
+ => 'apihelp-setnotificationtimestamp-example-pagetimestamp',
+ 'action=setnotificationtimestamp&generator=allpages&gapnamespace=2&token=123ABC'
+ => 'apihelp-setnotificationtimestamp-example-allpages',
);
}
diff --git a/includes/api/ApiStashEdit.php b/includes/api/ApiStashEdit.php
new file mode 100644
index 00000000..c4b717c7
--- /dev/null
+++ b/includes/api/ApiStashEdit.php
@@ -0,0 +1,412 @@
+<?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
+ */
+
+/**
+ * Prepare and edit in shared cache so that it can be reused on edit
+ *
+ * This endpoint can be called via AJAX as the user focuses on the edit
+ * summary box. By the time of submission, the parse may have already
+ * finished, and can be immediately used on page save. Certain parser
+ * functions like {{REVISIONID}} or {{CURRENTTIME}} may cause the cache
+ * to not be used on edit. Template and files used are check for changes
+ * since the output was generated. The cache TTL is also kept low for sanity.
+ *
+ * @ingroup API
+ * @since 1.25
+ */
+class ApiStashEdit extends ApiBase {
+ const ERROR_NONE = 'stashed';
+ const ERROR_PARSE = 'error_parse';
+ const ERROR_CACHE = 'error_cache';
+ const ERROR_UNCACHEABLE = 'uncacheable';
+
+ public function execute() {
+ global $wgMemc;
+
+ $user = $this->getUser();
+ $params = $this->extractRequestParams();
+
+ $page = $this->getTitleOrPageId( $params );
+ $title = $page->getTitle();
+
+ if ( !ContentHandler::getForModelID( $params['contentmodel'] )
+ ->isSupportedFormat( $params['contentformat'] )
+ ) {
+ $this->dieUsage( "Unsupported content model/format", 'badmodelformat' );
+ }
+
+ // Trim and fix newlines so the key SHA1's match (see RequestContext::getText())
+ $text = rtrim( str_replace( "\r\n", "\n", $params['text'] ) );
+ $textContent = ContentHandler::makeContent(
+ $text, $title, $params['contentmodel'], $params['contentformat'] );
+
+ $page = WikiPage::factory( $title );
+ if ( $page->exists() ) {
+ // Page exists: get the merged content with the proposed change
+ $baseRev = Revision::newFromPageId( $page->getId(), $params['baserevid'] );
+ if ( !$baseRev ) {
+ $this->dieUsage( "No revision ID {$params['baserevid']}", 'missingrev' );
+ }
+ $currentRev = $page->getRevision();
+ if ( !$currentRev ) {
+ $this->dieUsage( "No current revision of page ID {$page->getId()}", 'missingrev' );
+ }
+ // Merge in the new version of the section to get the proposed version
+ $editContent = $page->replaceSectionAtRev(
+ $params['section'],
+ $textContent,
+ $params['sectiontitle'],
+ $baseRev->getId()
+ );
+ if ( !$editContent ) {
+ $this->dieUsage( "Could not merge updated section.", 'replacefailed' );
+ }
+ if ( $currentRev->getId() == $baseRev->getId() ) {
+ // Base revision was still the latest; nothing to merge
+ $content = $editContent;
+ } else {
+ // Merge the edit into the current version
+ $baseContent = $baseRev->getContent();
+ $currentContent = $currentRev->getContent();
+ if ( !$baseContent || !$currentContent ) {
+ $this->dieUsage( "Missing content for page ID {$page->getId()}", 'missingrev' );
+ }
+ $handler = ContentHandler::getForModelID( $baseContent->getModel() );
+ $content = $handler->merge3( $baseContent, $editContent, $currentContent );
+ }
+ } else {
+ // New pages: use the user-provided content model
+ $content = $textContent;
+ }
+
+ if ( !$content ) { // merge3() failed
+ $this->getResult()->addValue( null,
+ $this->getModuleName(), array( 'status' => 'editconflict' ) );
+ return;
+ }
+
+ // The user will abort the AJAX request by pressing "save", so ignore that
+ ignore_user_abort( true );
+
+ // Get a key based on the source text, format, and user preferences
+ $key = self::getStashKey( $title, $content, $user );
+ // De-duplicate requests on the same key
+ if ( $user->pingLimiter( 'stashedit' ) ) {
+ $status = 'ratelimited';
+ } elseif ( $wgMemc->lock( $key, 0, 30 ) ) {
+ $unlocker = new ScopedCallback( function() use ( $key ) {
+ global $wgMemc;
+ $wgMemc->unlock( $key );
+ } );
+ $status = self::parseAndStash( $page, $content, $user );
+ } else {
+ $status = 'busy';
+ }
+
+ $this->getResult()->addValue( null, $this->getModuleName(), array( 'status' => $status ) );
+ }
+
+ /**
+ * @param WikiPage $page
+ * @param Content $content
+ * @param User $user
+ * @return integer ApiStashEdit::ERROR_* constant
+ * @since 1.25
+ */
+ public static function parseAndStash( WikiPage $page, Content $content, User $user ) {
+ global $wgMemc;
+
+ $format = $content->getDefaultFormat();
+ $editInfo = $page->prepareContentForEdit( $content, null, $user, $format, false );
+
+ if ( $editInfo && $editInfo->output ) {
+ $key = self::getStashKey( $page->getTitle(), $content, $user );
+
+ list( $stashInfo, $ttl ) = self::buildStashValue(
+ $editInfo->pstContent, $editInfo->output, $editInfo->timestamp
+ );
+
+ if ( $stashInfo ) {
+ $ok = $wgMemc->set( $key, $stashInfo, $ttl );
+ if ( $ok ) {
+ wfDebugLog( 'StashEdit', "Cached parser output for key '$key'." );
+ return self::ERROR_NONE;
+ } else {
+ wfDebugLog( 'StashEdit', "Failed to cache parser output for key '$key'." );
+ return self::ERROR_CACHE;
+ }
+ } else {
+ wfDebugLog( 'StashEdit', "Uncacheable parser output for key '$key'." );
+ return self::ERROR_UNCACHEABLE;
+ }
+ }
+
+ return self::ERROR_PARSE;
+ }
+
+ /**
+ * Attempt to cache PST content and corresponding parser output in passing
+ *
+ * This method can be called when the output was already generated for other
+ * reasons. Parsing should not be done just to call this method, however.
+ * $pstOpts must be that of the user doing the edit preview. If $pOpts does
+ * not match the options of WikiPage::makeParserOptions( 'canonical' ), this
+ * will do nothing. Provided the values are cacheable, they will be stored
+ * in memcached so that final edit submission might make use of them.
+ *
+ * @param Article|WikiPage $page Page title
+ * @param Content $content Proposed page content
+ * @param Content $pstContent The result of preSaveTransform() on $content
+ * @param ParserOutput $pOut The result of getParserOutput() on $pstContent
+ * @param ParserOptions $pstOpts Options for $pstContent (MUST be for prospective author)
+ * @param ParserOptions $pOpts Options for $pOut
+ * @param string $timestamp TS_MW timestamp of parser output generation
+ * @return bool Success
+ */
+ public static function stashEditFromPreview(
+ Page $page, Content $content, Content $pstContent, ParserOutput $pOut,
+ ParserOptions $pstOpts, ParserOptions $pOpts, $timestamp
+ ) {
+ global $wgMemc;
+
+ // getIsPreview() controls parser function behavior that references things
+ // like user/revision that don't exists yet. The user/text should already
+ // be set correctly by callers, just double check the preview flag.
+ if ( !$pOpts->getIsPreview() ) {
+ return false; // sanity
+ } elseif ( $pOpts->getIsSectionPreview() ) {
+ return false; // short-circuit (need the full content)
+ }
+
+ // PST parser options are for the user (handles signatures, etc...)
+ $user = $pstOpts->getUser();
+ // Get a key based on the source text, format, and user preferences
+ $key = self::getStashKey( $page->getTitle(), $content, $user );
+
+ // Parser output options must match cannonical options.
+ // Treat some options as matching that are different but don't matter.
+ $canonicalPOpts = $page->makeParserOptions( 'canonical' );
+ $canonicalPOpts->setIsPreview( true ); // force match
+ $canonicalPOpts->setTimestamp( $pOpts->getTimestamp() ); // force match
+ if ( !$pOpts->matches( $canonicalPOpts ) ) {
+ wfDebugLog( 'StashEdit', "Uncacheable preview output for key '$key' (options)." );
+ return false;
+ }
+
+ // Build a value to cache with a proper TTL
+ list( $stashInfo, $ttl ) = self::buildStashValue( $pstContent, $pOut, $timestamp );
+ if ( !$stashInfo ) {
+ wfDebugLog( 'StashEdit', "Uncacheable parser output for key '$key' (rev/TTL)." );
+ return false;
+ }
+
+ $ok = $wgMemc->set( $key, $stashInfo, $ttl );
+ if ( !$ok ) {
+ wfDebugLog( 'StashEdit', "Failed to cache preview parser output for key '$key'." );
+ } else {
+ wfDebugLog( 'StashEdit', "Cached preview output for key '$key'." );
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Check that a prepared edit is in cache and still up-to-date
+ *
+ * This method blocks if the prepared edit is already being rendered,
+ * waiting until rendering finishes before doing final validity checks.
+ *
+ * The cache is rejected if template or file changes are detected.
+ * Note that foreign template or file transclusions are not checked.
+ *
+ * The result is a map (pstContent,output,timestamp) with fields
+ * extracted directly from WikiPage::prepareContentForEdit().
+ *
+ * @param Title $title
+ * @param Content $content
+ * @param User $user User to get parser options from
+ * @return stdClass|bool Returns false on cache miss
+ */
+ public static function checkCache( Title $title, Content $content, User $user ) {
+ global $wgMemc;
+
+ $key = self::getStashKey( $title, $content, $user );
+ $editInfo = $wgMemc->get( $key );
+ if ( !is_object( $editInfo ) ) {
+ $start = microtime( true );
+ // We ignore user aborts and keep parsing. Block on any prior parsing
+ // so as to use it's results and make use of the time spent parsing.
+ if ( $wgMemc->lock( $key, 30, 30 ) ) {
+ $editInfo = $wgMemc->get( $key );
+ $wgMemc->unlock( $key );
+ }
+ $sec = microtime( true ) - $start;
+ if ( $sec > .01 ) {
+ wfDebugLog( 'StashEdit', "Waited $sec seconds on '$key'." );
+ }
+ }
+
+ if ( !is_object( $editInfo ) || !$editInfo->output ) {
+ wfDebugLog( 'StashEdit', "No cache value for key '$key'." );
+ return false;
+ }
+
+ $time = wfTimestamp( TS_UNIX, $editInfo->output->getTimestamp() );
+ if ( ( time() - $time ) <= 3 ) {
+ wfDebugLog( 'StashEdit', "Timestamp-based cache hit for key '$key'." );
+ return $editInfo; // assume nothing changed
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ // Check that no templates used in the output changed...
+ $cWhr = array(); // conditions to find changes/creations
+ $dWhr = array(); // conditions to find deletions
+ foreach ( $editInfo->output->getTemplateIds() as $ns => $stuff ) {
+ foreach ( $stuff as $dbkey => $revId ) {
+ $cWhr[] = array( 'page_namespace' => $ns, 'page_title' => $dbkey,
+ 'page_latest != ' . intval( $revId ) );
+ $dWhr[] = array( 'page_namespace' => $ns, 'page_title' => $dbkey );
+ }
+ }
+ $change = $dbr->selectField( 'page', '1', $dbr->makeList( $cWhr, LIST_OR ), __METHOD__ );
+ $n = $dbr->selectField( 'page', 'COUNT(*)', $dbr->makeList( $dWhr, LIST_OR ), __METHOD__ );
+ if ( $change || $n != count( $dWhr ) ) {
+ wfDebugLog( 'StashEdit', "Stale cache for key '$key'; template changed." );
+ return false;
+ }
+
+ // Check that no files used in the output changed...
+ $cWhr = array(); // conditions to find changes/creations
+ $dWhr = array(); // conditions to find deletions
+ foreach ( $editInfo->output->getFileSearchOptions() as $name => $options ) {
+ $cWhr[] = array( 'img_name' => $dbkey,
+ 'img_sha1 != ' . $dbr->addQuotes( strval( $options['sha1'] ) ) );
+ $dWhr[] = array( 'img_name' => $dbkey );
+ }
+ $change = $dbr->selectField( 'image', '1', $dbr->makeList( $cWhr, LIST_OR ), __METHOD__ );
+ $n = $dbr->selectField( 'image', 'COUNT(*)', $dbr->makeList( $dWhr, LIST_OR ), __METHOD__ );
+ if ( $change || $n != count( $dWhr ) ) {
+ wfDebugLog( 'StashEdit', "Stale cache for key '$key'; file changed." );
+ return false;
+ }
+
+ wfDebugLog( 'StashEdit', "Cache hit for key '$key'." );
+
+ return $editInfo;
+ }
+
+ /**
+ * Get the temporary prepared edit stash key for a user
+ *
+ * This key can be used for caching prepared edits provided:
+ * - a) The $user was used for PST options
+ * - b) The parser output was made from the PST using cannonical matching options
+ *
+ * @param Title $title
+ * @param Content $content
+ * @param User $user User to get parser options from
+ * @return string
+ */
+ protected static function getStashKey( Title $title, Content $content, User $user ) {
+ $hash = sha1( implode( ':', array(
+ $content->getModel(),
+ $content->getDefaultFormat(),
+ sha1( $content->serialize( $content->getDefaultFormat() ) ),
+ $user->getId() ?: md5( $user->getName() ), // account for user parser options
+ $user->getId() ? $user->getTouched() : '-' // handle preference change races
+ ) ) );
+
+ return wfMemcKey( 'prepared-edit', md5( $title->getPrefixedDBkey() ), $hash );
+ }
+
+ /**
+ * Build a value to store in memcached based on the PST content and parser output
+ *
+ * This makes a simple version of WikiPage::prepareContentForEdit() as stash info
+ *
+ * @param Content $pstContent
+ * @param ParserOutput $parserOutput
+ * @param string $timestamp TS_MW
+ * @return array (stash info array, TTL in seconds) or (null, 0)
+ */
+ protected static function buildStashValue(
+ Content $pstContent, ParserOutput $parserOutput, $timestamp
+ ) {
+ // If an item is renewed, mind the cache TTL determined by config and parser functions
+ $since = time() - wfTimestamp( TS_UNIX, $parserOutput->getTimestamp() );
+ $ttl = min( $parserOutput->getCacheExpiry() - $since, 5 * 60 );
+
+ if ( $ttl > 0 && !$parserOutput->getFlag( 'vary-revision' ) ) {
+ // Only store what is actually needed
+ $stashInfo = (object)array(
+ 'pstContent' => $pstContent,
+ 'output' => $parserOutput,
+ 'timestamp' => $timestamp
+ );
+ return array( $stashInfo, $ttl );
+ }
+
+ return array( null, 0 );
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'title' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'section' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ),
+ 'sectiontitle' => array(
+ ApiBase::PARAM_TYPE => 'string'
+ ),
+ 'text' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'baserevid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_REQUIRED => true
+ )
+ );
+ }
+
+ function needsToken() {
+ return 'csrf';
+ }
+
+ function mustBePosted() {
+ return true;
+ }
+
+ function isInternal() {
+ return true;
+ }
+}
diff --git a/includes/api/ApiTag.php b/includes/api/ApiTag.php
new file mode 100644
index 00000000..527c6cb1
--- /dev/null
+++ b/includes/api/ApiTag.php
@@ -0,0 +1,177 @@
+<?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 API
+ * @since 1.25
+ */
+class ApiTag extends ApiBase {
+
+ protected function getAvailableTags() {
+ return ChangeTags::listExplicitlyDefinedTags();
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ // make sure the user is allowed
+ if ( !$this->getUser()->isAllowed( 'changetags' ) ) {
+ $this->dieUsage( "You don't have permission to add or remove change tags from individual edits",
+ 'permissiondenied' );
+ }
+
+ // validate and process each revid, rcid and logid
+ $this->requireAtLeastOneParameter( $params, 'revid', 'rcid', 'logid' );
+ $ret = array();
+ if ( $params['revid'] ) {
+ foreach ( $params['revid'] as $id ) {
+ $ret[] = $this->processIndividual( 'revid', $params, $id );
+ }
+ }
+ if ( $params['rcid'] ) {
+ foreach ( $params['rcid'] as $id ) {
+ $ret[] = $this->processIndividual( 'rcid', $params, $id );
+ }
+ }
+ if ( $params['logid'] ) {
+ foreach ( $params['logid'] as $id ) {
+ $ret[] = $this->processIndividual( 'logid', $params, $id );
+ }
+ }
+
+ ApiResult::setIndexedTagName( $ret, 'result' );
+ $this->getResult()->addValue( null, $this->getModuleName(), $ret );
+ }
+
+ protected static function validateLogId( $logid ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $result = $dbr->selectField( 'logging', 'log_id', array( 'log_id' => $logid ),
+ __METHOD__ );
+ return (bool)$result;
+ }
+
+ protected function processIndividual( $type, $params, $id ) {
+ $idResult = array( $type => $id );
+
+ // validate the ID
+ $valid = false;
+ switch ( $type ) {
+ case 'rcid':
+ $valid = RecentChange::newFromId( $id );
+ break;
+ case 'revid':
+ $valid = Revision::newFromId( $id );
+ break;
+ case 'logid':
+ $valid = self::validateLogId( $id );
+ break;
+ }
+
+ if ( !$valid ) {
+ $idResult['status'] = 'error';
+ $idResult += $this->parseMsg( array( "nosuch$type", $id ) );
+ return $idResult;
+ }
+
+ $status = ChangeTags::updateTagsWithChecks( $params['add'],
+ $params['remove'],
+ ( $type === 'rcid' ? $id : null ),
+ ( $type === 'revid' ? $id : null ),
+ ( $type === 'logid' ? $id : null ),
+ null,
+ $params['reason'],
+ $this->getUser() );
+
+ if ( !$status->isOK() ) {
+ if ( $status->hasMessage( 'actionthrottledtext' ) ) {
+ $idResult['status'] = 'skipped';
+ } else {
+ $idResult['status'] = 'failure';
+ $idResult['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status, 'error' );
+ }
+ } else {
+ $idResult['status'] = 'success';
+ if ( is_null( $status->value->logId ) ) {
+ $idResult['noop'] = '';
+ } else {
+ $idResult['actionlogid'] = $status->value->logId;
+ $idResult['added'] = $status->value->addedTags;
+ ApiResult::setIndexedTagName( $idResult['added'], 't' );
+ $idResult['removed'] = $status->value->removedTags;
+ ApiResult::setIndexedTagName( $idResult['removed'], 't' );
+ }
+ }
+ return $idResult;
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'rcid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'revid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'logid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'add' => array(
+ ApiBase::PARAM_TYPE => $this->getAvailableTags(),
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'remove' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'reason' => array(
+ ApiBase::PARAM_DFLT => '',
+ ),
+ );
+ }
+
+ public function needsToken() {
+ return 'csrf';
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=tag&revid=123&add=vandalism&token=123ABC'
+ => 'apihelp-tag-example-rev',
+ 'action=tag&logid=123&remove=spam&reason=Wrongly+applied&token=123ABC'
+ => 'apihelp-tag-example-log',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Tag';
+ }
+}
diff --git a/includes/api/ApiTokens.php b/includes/api/ApiTokens.php
index 9287fe6e..4d7fc5a0 100644
--- a/includes/api/ApiTokens.php
+++ b/includes/api/ApiTokens.php
@@ -34,9 +34,12 @@ class ApiTokens extends ApiBase {
$this->setWarning(
"action=tokens has been deprecated. Please use action=query&meta=tokens instead."
);
+ $this->logFeatureUsage( "action=tokens" );
$params = $this->extractRequestParams();
- $res = array();
+ $res = array(
+ ApiResult::META_TYPE => 'assoc',
+ );
$types = $this->getTokenTypes();
foreach ( $params['type'] as $type ) {
@@ -53,8 +56,9 @@ class ApiTokens extends ApiBase {
}
private function getTokenTypes() {
- // If we're in JSON callback mode, no tokens can be obtained
- if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
+ // If we're in a mode that breaks the same-origin policy, no tokens can
+ // be obtained
+ if ( $this->lacksSameOriginSecurity() ) {
return array();
}
@@ -62,20 +66,22 @@ class ApiTokens extends ApiBase {
if ( $types ) {
return $types;
}
- wfProfileIn( __METHOD__ );
$types = array( 'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' ) );
$names = array( 'edit', 'delete', 'protect', 'move', 'block', 'unblock',
'email', 'import', 'watch', 'options' );
foreach ( $names as $name ) {
$types[$name] = array( 'ApiQueryInfo', 'get' . ucfirst( $name ) . 'Token' );
}
- wfRunHooks( 'ApiTokensGetTokenTypes', array( &$types ) );
+ Hooks::run( 'ApiTokensGetTokenTypes', array( &$types ) );
ksort( $types );
- wfProfileOut( __METHOD__ );
return $types;
}
+ public function isDeprecated() {
+ return true;
+ }
+
public function getAllowedParams() {
return array(
'type' => array(
@@ -86,23 +92,12 @@ class ApiTokens extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'type' => 'Type of token(s) to request'
- );
- }
-
- public function getDescription() {
- return array(
- 'This module is deprecated in favor of action=query&meta=tokens.',
- 'Gets tokens for data-modifying actions.'
- );
- }
-
- protected function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=tokens' => 'Retrieve an edit token (the default)',
- 'api.php?action=tokens&type=email|move' => 'Retrieve an email token and a move token'
+ 'action=tokens'
+ => 'apihelp-tokens-example-edit',
+ 'action=tokens&type=email|move'
+ => 'apihelp-tokens-example-emailmove',
);
}
}
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index 2854a825..1af83ba3 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -93,30 +93,16 @@ class ApiUnblock extends ApiBase {
);
}
- 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",
- 'reason' => 'Reason for unblock',
- );
- }
-
- public function getDescription() {
- return 'Unblock a user.';
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=unblock&id=105',
- 'api.php?action=unblock&user=Bob&reason=Sorry%20Bob'
+ 'action=unblock&id=105'
+ => 'apihelp-unblock-example-id',
+ 'action=unblock&user=Bob&reason=Sorry%20Bob'
+ => 'apihelp-unblock-example-user',
);
}
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index 07aad9f5..c23e9ff6 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -69,7 +69,7 @@ class ApiUndelete extends ApiBase {
}
if ( $retval[1] ) {
- wfRunHooks( 'FileUndeleteComplete',
+ Hooks::run( 'FileUndeleteComplete',
array( $titleObj, $params['fileids'], $this->getUser(), $params['reason'] ) );
}
@@ -117,39 +117,17 @@ class ApiUndelete extends ApiBase {
);
}
- public function getParamDescription() {
- return array(
- 'title' => 'Title of the page you want to restore',
- 'reason' => 'Reason for restoring',
- '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, and a list',
- 'of deleted file ids can be retrieved through list=filearchive.'
- );
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- 'api.php?action=undelete&title=Main%20Page&token=123ABC&reason=Restoring%20main%20page',
- 'api.php?action=undelete&title=Main%20Page&token=123ABC&timestamps=20070703220045|20070702194856'
+ 'action=undelete&title=Main%20Page&token=123ABC&reason=Restoring%20main%20page'
+ => 'apihelp-undelete-example-page',
+ 'action=undelete&title=Main%20Page&token=123ABC' .
+ '&timestamps=2007-07-03T22:00:45Z|2007-07-02T19:48:56Z'
+ => 'apihelp-undelete-example-revisions',
);
}
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index 657181b7..74ae05a8 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -64,7 +64,7 @@ class ApiUpload extends ApiBase {
$this->dieUsage( 'No upload module set', 'nomodule' );
}
} catch ( UploadStashException $e ) { // XXX: don't spam exception log
- $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
+ $this->handleStashException( $e );
}
// First check permission to upload
@@ -112,7 +112,7 @@ class ApiUpload extends ApiBase {
$result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
}
} catch ( UploadStashException $e ) { // XXX: don't spam exception log
- $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' );
+ $this->handleStashException( $e );
}
$this->getResult()->addValue( null, $this->getModuleName(), $result );
@@ -159,7 +159,9 @@ class ApiUpload extends ApiBase {
if ( $warnings && count( $warnings ) > 0 ) {
$result['warnings'] = $warnings;
}
- } catch ( MWException $e ) {
+ } catch ( UploadStashException $e ) {
+ $this->handleStashException( $e );
+ } catch ( Exception $e ) {
$this->dieUsage( $e->getMessage(), 'stashfailed' );
}
@@ -180,7 +182,7 @@ class ApiUpload extends ApiBase {
try {
$result['filekey'] = $this->performStash();
$result['sessionkey'] = $result['filekey']; // backwards compatibility
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$result['warnings']['stashfailed'] = $e->getMessage();
}
@@ -205,7 +207,9 @@ class ApiUpload extends ApiBase {
if ( $this->mParams['offset'] == 0 ) {
try {
$filekey = $this->performStash();
- } catch ( MWException $e ) {
+ } catch ( UploadStashException $e ) {
+ $this->handleStashException( $e );
+ } catch ( Exception $e ) {
// FIXME: Error handling here is wrong/different from rest of this
$this->dieUsage( $e->getMessage(), 'stashfailed' );
}
@@ -214,7 +218,11 @@ class ApiUpload extends ApiBase {
$status = $this->mUpload->addChunk(
$chunkPath, $chunkSize, $this->mParams['offset'] );
if ( !$status->isGood() ) {
- $this->dieUsage( $status->getWikiText(), 'stashfailed' );
+ $extradata = array(
+ 'offset' => $this->mUpload->getOffset(),
+ );
+
+ $this->dieUsage( $status->getWikiText(), 'stashfailed', 0, $extradata );
return array();
}
@@ -223,11 +231,12 @@ class ApiUpload extends ApiBase {
// Check we added the last chunk:
if ( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
if ( $this->mParams['async'] ) {
- $progress = UploadBase::getSessionStatus( $filekey );
+ $progress = UploadBase::getSessionStatus( $this->getUser(), $filekey );
if ( $progress && $progress['result'] === 'Poll' ) {
$this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' );
}
UploadBase::setSessionStatus(
+ $this->getUser(),
$filekey,
array( 'result' => 'Poll',
'stage' => 'queued', 'status' => Status::newGood() )
@@ -272,16 +281,17 @@ class ApiUpload extends ApiBase {
*/
private function performStash() {
try {
- $stashFile = $this->mUpload->stashFile();
+ $stashFile = $this->mUpload->stashFile( $this->getUser() );
if ( !$stashFile ) {
throw new MWException( 'Invalid stashed file' );
}
$fileKey = $stashFile->getFileKey();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
wfDebug( __METHOD__ . ' ' . $message . "\n" );
- throw new MWException( $message );
+ $className = get_class( $e );
+ throw new $className( $message );
}
return $fileKey;
@@ -300,7 +310,7 @@ class ApiUpload extends ApiBase {
try {
$data['filekey'] = $this->performStash();
$data['sessionkey'] = $data['filekey'];
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$data['stashfailed'] = $e->getMessage();
}
$data['invalidparameter'] = $parameter;
@@ -327,7 +337,7 @@ class ApiUpload extends ApiBase {
// Status report for "upload to stash"/"upload from stash"
if ( $this->mParams['filekey'] && $this->mParams['checkstatus'] ) {
- $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
if ( !$progress ) {
$this->dieUsage( 'No result in status data', 'missingresult' );
} elseif ( !$progress['status']->isGood() ) {
@@ -504,20 +514,20 @@ class ApiUpload extends ApiBase {
'filetype' => $verification['finalExt'],
'allowed' => array_values( array_unique( $this->getConfig()->get( 'FileExtensions' ) ) )
);
- $this->getResult()->setIndexedTagName( $extradata['allowed'], 'ext' );
+ ApiResult::setIndexedTagName( $extradata['allowed'], 'ext' );
$msg = "Filetype not permitted: ";
if ( isset( $verification['blacklistedExt'] ) ) {
$msg .= join( ', ', $verification['blacklistedExt'] );
$extradata['blacklisted'] = array_values( $verification['blacklistedExt'] );
- $this->getResult()->setIndexedTagName( $extradata['blacklisted'], 'ext' );
+ ApiResult::setIndexedTagName( $extradata['blacklisted'], 'ext' );
} else {
$msg .= $verification['finalExt'];
}
$this->dieUsage( $msg, 'filetype-banned', 0, $extradata );
break;
case UploadBase::VERIFICATION_ERROR:
- $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
+ ApiResult::setIndexedTagName( $verification['details'], 'detail' );
$this->dieUsage( 'This file did not pass file verification', 'verification-error',
0, array( 'details' => $verification['details'] ) );
break;
@@ -549,7 +559,7 @@ class ApiUpload extends ApiBase {
if ( $warnings ) {
// Add indices
$result = $this->getResult();
- $result->setIndexedTagName( $warnings, 'warning' );
+ ApiResult::setIndexedTagName( $warnings, 'warning' );
if ( isset( $warnings['duplicate'] ) ) {
$dupes = array();
@@ -557,7 +567,7 @@ class ApiUpload extends ApiBase {
foreach ( $warnings['duplicate'] as $dupe ) {
$dupes[] = $dupe->getName();
}
- $result->setIndexedTagName( $dupes, 'duplicate' );
+ ApiResult::setIndexedTagName( $dupes, 'duplicate' );
$warnings['duplicate'] = $dupes;
}
@@ -576,6 +586,41 @@ class ApiUpload extends ApiBase {
}
/**
+ * Handles a stash exception, giving a useful error to the user.
+ * @param Exception $e The exception we encountered.
+ */
+ protected function handleStashException( $e ) {
+ $exceptionType = get_class( $e );
+
+ switch ( $exceptionType ) {
+ case 'UploadStashFileNotFoundException':
+ $this->dieUsage( 'Could not find the file in the stash: ' . $e->getMessage(), 'stashedfilenotfound' );
+ break;
+ case 'UploadStashBadPathException':
+ $this->dieUsage( 'File key of improper format or otherwise invalid: ' . $e->getMessage(), 'stashpathinvalid' );
+ break;
+ case 'UploadStashFileException':
+ $this->dieUsage( 'Could not store upload in the stash: ' . $e->getMessage(), 'stashfilestorage' );
+ break;
+ case 'UploadStashZeroLengthFileException':
+ $this->dieUsage( 'File is of zero length, and could not be stored in the stash: ' . $e->getMessage(), 'stashzerolength' );
+ break;
+ case 'UploadStashNotLoggedInException':
+ $this->dieUsage( 'Not logged in: ' . $e->getMessage(), 'stashnotloggedin' );
+ break;
+ case 'UploadStashWrongOwnerException':
+ $this->dieUsage( 'Wrong owner: ' . $e->getMessage(), 'stashwrongowner' );
+ break;
+ case 'UploadStashNoSuchKeyException':
+ $this->dieUsage( 'No such filekey: ' . $e->getMessage(), 'stashnosuchfilekey' );
+ break;
+ default:
+ $this->dieUsage( $exceptionType . ": " . $e->getMessage(), 'stasherror' );
+ break;
+ }
+ }
+
+ /**
* Perform the actual upload. Returns a suitable result array on success;
* dies on failure.
*
@@ -612,11 +657,12 @@ class ApiUpload extends ApiBase {
// No errors, no warnings: do the upload
if ( $this->mParams['async'] ) {
- $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] );
+ $progress = UploadBase::getSessionStatus( $this->getUser(), $this->mParams['filekey'] );
if ( $progress && $progress['result'] === 'Poll' ) {
$this->dieUsage( "Upload from stash already in progress.", 'publishfailed' );
}
UploadBase::setSessionStatus(
+ $this->getUser(),
$this->mParams['filekey'],
array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() )
);
@@ -650,7 +696,7 @@ class ApiUpload extends ApiBase {
);
}
- $this->getResult()->setIndexedTagName( $error, 'error' );
+ ApiResult::setIndexedTagName( $error, 'error' );
$this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
}
$result['result'] = 'Success';
@@ -730,59 +776,17 @@ class ApiUpload extends ApiBase {
return $params;
}
- public function getParamDescription() {
- $params = array(
- 'filename' => 'Target filename',
- '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',
- '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.',
-
- 'chunk' => 'Chunk contents',
- 'offset' => 'Offset of chunk in bytes',
- 'filesize' => 'Filesize of entire upload',
-
- 'async' => 'Make potentially large file operations asynchronous when possible',
- 'asyncdownload' => 'Make fetching a URL asynchronous',
- 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
- 'statuskey' => 'Fetch the upload status for this file key (upload by URL)',
- 'checkstatus' => 'Only fetch the upload status for the given file key',
- );
-
- return $params;
- }
-
- public function getDescription() {
- return array(
- 'Upload a file, or get the status of pending uploads. Several methods are available:',
- ' * Upload file contents directly, using the "file" parameter',
- ' * 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".',
- );
- }
-
public function needsToken() {
return 'csrf';
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- '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&token=123ABC'
- => 'Complete an upload that failed due to warnings',
+ 'action=upload&filename=Wiki.png' .
+ '&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
+ => 'apihelp-upload-example-url',
+ 'action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
+ => 'apihelp-upload-example-filekey',
);
}
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index c3ceb345..3ccdde25 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -32,12 +32,28 @@ class ApiUserrights extends ApiBase {
private $mUser = null;
+ /**
+ * Get a UserrightsPage object, or subclass.
+ * @return UserrightsPage
+ */
+ protected function getUserRightsPage() {
+ return new UserrightsPage;
+ }
+
+ /**
+ * Get all available groups.
+ * @return array
+ */
+ protected function getAllGroups() {
+ return User::getAllGroups();
+ }
+
public function execute() {
$params = $this->extractRequestParams();
$user = $this->getUrUser( $params );
- $form = new UserrightsPage;
+ $form = $this->getUserRightsPage();
$form->setContext( $this->getContext() );
$r['user'] = $user->getName();
$r['userid'] = $user->getId();
@@ -47,8 +63,8 @@ class ApiUserrights extends ApiBase {
);
$result = $this->getResult();
- $result->setIndexedTagName( $r['added'], 'group' );
- $result->setIndexedTagName( $r['removed'], 'group' );
+ ApiResult::setIndexedTagName( $r['added'], 'group' );
+ ApiResult::setIndexedTagName( $r['removed'], 'group' );
$result->addValue( null, $this->getModuleName(), $r );
}
@@ -65,7 +81,7 @@ class ApiUserrights extends ApiBase {
$user = isset( $params['user'] ) ? $params['user'] : '#' . $params['userid'];
- $form = new UserrightsPage;
+ $form = $this->getUserRightsPage();
$form->setContext( $this->getContext() );
$status = $form->fetchUser( $user );
if ( !$status->isOK() ) {
@@ -94,37 +110,23 @@ class ApiUserrights extends ApiBase {
ApiBase::PARAM_TYPE => 'integer',
),
'add' => array(
- ApiBase::PARAM_TYPE => User::getAllGroups(),
+ ApiBase::PARAM_TYPE => $this->getAllGroups(),
ApiBase::PARAM_ISMULTI => true
),
'remove' => array(
- ApiBase::PARAM_TYPE => User::getAllGroups(),
+ ApiBase::PARAM_TYPE => $this->getAllGroups(),
ApiBase::PARAM_ISMULTI => true
),
'reason' => array(
ApiBase::PARAM_DFLT => ''
- )
- );
- }
-
- 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' => array(
- /* Standard description automatically prepended */
- 'For compatibility, the token used in the web UI is also accepted.'
+ // Standard definition automatically inserted
+ ApiBase::PARAM_HELP_MSG_APPEND => array( 'api-help-param-token-webui' ),
),
- 'reason' => 'Reason for the change',
);
}
- public function getDescription() {
- return 'Add/remove a user to/from groups.';
- }
-
public function needsToken() {
return 'userrights';
}
@@ -133,10 +135,12 @@ class ApiUserrights extends ApiBase {
return $this->getUrUser( $params )->getName();
}
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- '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'
+ 'action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
+ => 'apihelp-userrights-example-user',
+ 'action=userrights&userid=123&add=bot&remove=sysop|bureaucrat&token=123ABC'
+ => 'apihelp-userrights-example-userid',
);
}
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index e6a660b3..85d051de 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -44,7 +44,8 @@ class ApiWatch extends ApiBase {
$params = $this->extractRequestParams();
- $this->getResult()->beginContinuation( $params['continue'], array(), array() );
+ $continuationManager = new ApiContinuationManager( $this, array(), array() );
+ $this->setContinuationManager( $continuationManager );
$pageSet = $this->getPageSet();
// by default we use pageset to extract the page to work on.
@@ -69,7 +70,7 @@ class ApiWatch extends ApiBase {
$r = $this->watchTitle( $title, $user, $params );
$res[] = $r;
}
- $this->getResult()->setIndexedTagName( $res, 'w' );
+ ApiResult::setIndexedTagName( $res, 'w' );
} else {
// dont allow use of old title parameter with new pageset parameters.
$extraParams = array_keys( array_filter( $pageSet->extractRequestParams(), function ( $x ) {
@@ -92,7 +93,9 @@ class ApiWatch extends ApiBase {
$res = $this->watchTitle( $title, $user, $params, true );
}
$this->getResult()->addValue( null, $this->getModuleName(), $res );
- $this->getResult()->endContinuation();
+
+ $this->setContinuationManager( null );
+ $continuationManager->setContinuationIntoResult( $this->getResult() );
}
private function watchTitle( Title $title, User $user, array $params,
@@ -104,37 +107,22 @@ class ApiWatch extends ApiBase {
$res = array( 'title' => $title->getPrefixedText() );
- // 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()
- ) {
- $oldLang = $this->getContext()->getLanguage(); // Backup language
- $this->getContext()->setLanguage( Language::factory( $params['uselang'] ) );
- }
-
if ( $params['unwatch'] ) {
$status = UnwatchAction::doUnwatch( $title, $user );
+ $res['unwatched'] = $status->isOK();
if ( $status->isOK() ) {
- $res['unwatched'] = '';
$res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )
->title( $title )->parseAsBlock();
}
} else {
$status = WatchAction::doWatch( $title, $user );
+ $res['watched'] = $status->isOK();
if ( $status->isOK() ) {
- $res['watched'] = '';
$res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )
->title( $title )->parseAsBlock();
}
}
- if ( !is_null( $oldLang ) ) {
- $this->getContext()->setLanguage( $oldLang ); // Reset language to $oldLang
- }
-
if ( !$status->isOK() ) {
if ( $compatibilityMode ) {
$this->dieStatus( $status );
@@ -176,8 +164,9 @@ class ApiWatch extends ApiBase {
ApiBase::PARAM_DEPRECATED => true
),
'unwatch' => false,
- 'uselang' => null,
- 'continue' => '',
+ 'continue' => array(
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ),
);
if ( $flags ) {
$result += $this->getPageSet()->getFinalParams( $flags );
@@ -186,25 +175,14 @@ class ApiWatch extends ApiBase {
return $result;
}
- public function getParamDescription() {
- $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',
- 'continue' => 'When more results are available, use this to continue',
- );
- }
-
- public function getDescription() {
- return 'Add or remove pages from/to the current user\'s watchlist.';
- }
-
- public function getExamples() {
+ protected function getExamplesMessages() {
return array(
- '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"',
+ 'action=watch&titles=Main_Page&token=123ABC'
+ => 'apihelp-watch-example-watch',
+ 'action=watch&titles=Main_Page&unwatch=&token=123ABC'
+ => 'apihelp-watch-example-unwatch',
+ 'action=watch&generator=allpages&gapnamespace=0&token=123ABC'
+ => 'apihelp-watch-example-generator',
);
}
diff --git a/includes/api/i18n/ar.json b/includes/api/i18n/ar.json
new file mode 100644
index 00000000..aa456f00
--- /dev/null
+++ b/includes/api/i18n/ar.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "Meno25",
+ "أحمد المحمودي",
+ "Khaled",
+ "Fatz"
+ ]
+ },
+ "apihelp-main-param-format": "صيغة الخرج.",
+ "apihelp-block-description": "منع مستخدم.",
+ "apihelp-block-param-reason": "السبب للمنع.",
+ "apihelp-block-param-nocreate": "امنع إنشاء الحسابات.",
+ "apihelp-compare-param-fromtitle": "العنوان الأول للمقارنة.",
+ "apihelp-compare-param-fromid": "رقم الصفحة الأول للمقارنة.",
+ "apihelp-compare-param-fromrev": "أول مراجعة للمقارنة.",
+ "apihelp-compare-param-totitle": "العنوان الثاني للمقارنة.",
+ "apihelp-compare-param-toid": "رقم الصفحة الثاني للمقارنة.",
+ "apihelp-compare-param-torev": "المراجعة الثانية للمقارنة.",
+ "apihelp-createaccount-param-name": "اسم المستخدم.",
+ "apihelp-delete-description": "حذف صفحة.",
+ "apihelp-delete-param-unwatch": "أزل الصفحة من قائمة مراقبتك.",
+ "apihelp-edit-description": "إنشاء وتعديل الصفحات.",
+ "apihelp-edit-param-watch": "أضف الصفحة إلى لائحة مراقبة المستعمل الحالي",
+ "apihelp-emailuser-description": "مراسلة المستخدم",
+ "apihelp-patrol-example-rcid": "ابحث عن تغيير جديد",
+ "apihelp-query+prefixsearch-param-offset": "عدد النتائج المراد تخطيها."
+}
diff --git a/includes/api/i18n/av.json b/includes/api/i18n/av.json
new file mode 100644
index 00000000..df85c0b6
--- /dev/null
+++ b/includes/api/i18n/av.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Аль-Гимравий"
+ ]
+ },
+ "apihelp-block-param-user": "Нужее блокалда лъезе бокьун вугев гІахьалчиясул цІар, IP-адрес яги IP-адресазул диапазон"
+}
diff --git a/includes/api/i18n/awa.json b/includes/api/i18n/awa.json
new file mode 100644
index 00000000..d0945921
--- /dev/null
+++ b/includes/api/i18n/awa.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "1AnuraagPandey"
+ ]
+ },
+ "apihelp-block-description": "सदस्य कय अवरोधित करा जाय।",
+ "apihelp-block-param-reason": "ब्लाक करेकै कारण",
+ "apihelp-block-param-nocreate": "खाते बनावेकै रोका जाय",
+ "apihelp-edit-param-minor": "छोट संपादन"
+}
diff --git a/includes/api/i18n/be-tarask.json b/includes/api/i18n/be-tarask.json
new file mode 100644
index 00000000..a09cb5ab
--- /dev/null
+++ b/includes/api/i18n/be-tarask.json
@@ -0,0 +1,55 @@
+{
+ "@metadata": {
+ "authors": [
+ "Red Winged Duck"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Дакумэнтацыя]]\n* [[mw:API:FAQ|Частыя пытаньні]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Сьпіс рассылкі]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-аб’явы]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Памылкі і запыты]\n</div>\n<strong>Статус:</strong> усе магчымасьці на гэтай старонцы павінны працаваць, але API знаходзіцца ў актыўнай распрацоўцы і можа зьмяняцца ў любы момант. Падпісвайцеся на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ рассылку mediawiki-api-announce] дзеля паведамленьняў пра абнаўленьні.\n\n<strong>Памылковыя запыты:</strong> калі да API дасылаюцца памылковыя запыты, HTTP-загаловак будзе дасланы з ключом «MediaWiki-API-Error», а потым значэньне загалоўку і код памылкі будуць выстаўленыя на аднолькавае значэньне. Дзеля дадатковай інфармацыі глядзіце [[mw:API:Errors_and_warnings|API: Памылкі і папярэджаньні]].",
+ "apihelp-main-param-action": "Дзеяньне для выкананьня.",
+ "apihelp-main-param-format": "Фармат вываду.",
+ "apihelp-main-param-maxlag": "Максымальная затрымка можа ўжывацца, калі MediaWiki ўсталяваная ў клястэр з рэплікаванай базай зьвестак. Дзеля захаваньня дзеяньняў, якія выклікаюць затрымку рэплікацыі, гэты парамэтар можа прымусіць кліента чакаць, пакуль затрымка рэплікацыі меншая за яго значэньне. У выпадку доўгай затрымкі, вяртаецца код памылкі <samp>maxlag</samp> з паведамленьнем кшталту <samp>Чаканьне $host: $lag сэкундаў затрымкі</samp>.<br />Глядзіце [[mw:Manual:Maxlag_parameter|Інструкцыя:Парамэтар maxlag]] дзеля дадатковай інфармацыі.",
+ "apihelp-main-param-smaxage": "Выстаўце загаловак <code>s-maxage</code> на зададзеную колькасьць сэкундаў. Памылкі ніколі не кэшуюцца.",
+ "apihelp-main-param-maxage": "Выстаўляе загаловак <code>max-age</code> на зададзеную колькасьць сэкундаў. Памылкі ніколі не кэшуюцца.",
+ "apihelp-main-param-assert": "Упэўніцеся, што ўдзельнік увайшоў у сыстэму, калі зададзена <kbd>user</kbd>, або мае правы робата, калі зададзена <kbd>bot</kbd>.",
+ "apihelp-main-param-requestid": "Любое значэньне, пададзенае тут, будзе ўключанае ў адказ. Можа быць выкарыстанае для адрозьненьня запытаў.",
+ "apihelp-main-param-servedby": "Уключае ў вынік назву сэрвэра, які апрацаваў запыт.",
+ "apihelp-main-param-curtimestamp": "Уключае ў вынік пазнаку актуальнага часу.",
+ "apihelp-main-param-origin": "Пры звароце да API з дапамогай міждамэннага AJAX-запыту (CORS), выстаўце парамэтру значэньне зыходнага дамэну. Ён мусіць быць уключаны ў кожны папярэдні запыт і такім чынам мусіць быць часткай URI-запыту (ня цела POST). Ён мусіць супадаць з адной з крыніц у загалоўку <code>Origin</code>, павінна быць зададзена нешта кшталту <kbd>https://en.wikipedia.org</kbd> або <kbd>https://meta.wikimedia.org</kbd>. Калі парамэтар не супадае з загалоўкам <code>Origin</code>, будзе вернуты адказ з кодам памылкі 403. Калі парамэтар супадае з загалоўкам <code>Origin</code> і крыніца знаходзіцца ў белым сьпісе, будзе выстаўлены загаловак <code>Access-Control-Allow-Origin</code>.",
+ "apihelp-main-param-uselang": "Мова для выкарыстаньня ў перакладах паведамленьняў. Сьпіс кодаў можа быць атрыманы з <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> з <kbd>siprop=languages</kbd>, або трэба вызначыць <kbd>user</kbd>, каб ужываць наладкі мовы цяперашняга карыстальніка, або вызначыць <kbd>content</kbd>, каб ужываць мову зьместу гэтай вікі.",
+ "apihelp-block-description": "Блякаваньне ўдзельніка.",
+ "apihelp-block-param-user": "Імя ўдзельніка, IP-адрас або IP-дыяпазон, якія вы хочаце заблякаваць.",
+ "apihelp-block-param-expiry": "Час заканчэньня. Можа быць адносным (напрыклад, <kbd>5 months</kbd> або <kbd>2 weeks</kbd>) ці абсалютным (напрыклад, <kbd>2014-09-18T12:34:56Z</kbd>). Калі выстаўлены на <kbd>infinite</kbd>, <kbd>indefinite</kbd> ці <kbd>never</kbd>, блякаваньне будзе бестэрміновым.",
+ "apihelp-block-param-reason": "Прычына блякаваньня.",
+ "apihelp-block-param-anononly": "Заблякаваць толькі ананімных удзельнікаў (напрыклад, забараніць ананімныя праўкі з гэтага IP-адрасу).",
+ "apihelp-block-param-nocreate": "Забарона стварэньня рахункаў.",
+ "apihelp-block-param-autoblock": "Аўтаматычна блякаваць апошні ўжыты IP-адрас, а таксама ўсе наступныя IP-адрасы, зь якіх будуць спробы ўваходу.",
+ "apihelp-block-param-noemail": "Забараняе ўдзельніку дасылаць лісты электроннай пошты празь вікі (трэба мець права <code>blockemail</code>).",
+ "apihelp-block-param-hidename": "Схаваць імя ўдзельніка з журналу блякаваньняў (патрабуе права <code>hideuser</code>).",
+ "apihelp-block-param-allowusertalk": "Дазволіць удзельніку рэдагаваць уласную старонку гутарак (залежыць ад <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "Калі ўдзельнік ужо заблякаваны, перапісаць дзейнае блякаваньне.",
+ "apihelp-block-param-watchuser": "Назіраць за старонкай удзельніка або старонкай IP-адрасу, а таксама старонкай гутарак.",
+ "apihelp-block-example-ip-simple": "Заблякаваць IP-адрас <kbd>192.0.2.5</kbd> на тры дні з прычынай <kbd>First strike</kbd>.",
+ "apihelp-block-example-user-complex": "Заблякаваць удзельніка <kbd>Vandal</kbd> назаўсёды з прычынай <kbd>Vandalism</kbd>, а таксама забараніць стварэньне новых рахункаў і адсылку лістоў электроннай поштай.",
+ "apihelp-clearhasmsg-description": "Ачышчае сьцяг <code>hasmsg</code> для актуальнага карыстальніка.",
+ "apihelp-clearhasmsg-example-1": "Ачыстка сьцягу <code>hasmsg</code> для актуальнага карыстальніка",
+ "apihelp-compare-description": "Атрымаць розьніцу паміж 2 старонкамі.\n\nВы мусіце перадаць нумар вэрсіі, назву або ID старонкі для абодвух «from» і «to».",
+ "apihelp-compare-param-fromtitle": "Першая назва для параўнаньня.",
+ "apihelp-compare-param-fromid": "ID першай старонкі для параўнаньня.",
+ "apihelp-compare-param-fromrev": "Першая вэрсія для параўнаньня.",
+ "apihelp-compare-param-totitle": "Другая назва для параўнаньня.",
+ "apihelp-compare-param-toid": "ID другой старонкі для параўнаньня.",
+ "apihelp-compare-param-torev": "Другая вэрсія для параўнаньня.",
+ "apihelp-compare-example-1": "Паказвае розьніцу паміж вэрсіямі 1 і 2",
+ "apihelp-createaccount-description": "Стварэньне новага рахунку ўдзельніка.",
+ "apihelp-createaccount-param-name": "Імя ўдзельніка.",
+ "apihelp-createaccount-param-password": "Пароль (ігнаруецца, калі выстаўлена <var>$1mailpassword</var>).",
+ "apihelp-createaccount-param-domain": "Дамэн для вонкавай аўтэнтыфікацыі (неабавязкова).",
+ "apihelp-createaccount-param-token": "Маркер стварэньня рахунку, атрыманы пры першым запыце.",
+ "apihelp-createaccount-param-email": "Адрас электроннай пошты ўдзельніка (неабавязкова).",
+ "apihelp-createaccount-param-realname": "Сапраўднае імя ўдзельніка (неабавязкова).",
+ "apihelp-createaccount-param-mailpassword": "Калі ўсталяванае любое значэньне, выпадковы пароль будзе дасланы карыстальніку на электронную пошту.",
+ "apihelp-createaccount-param-reason": "Неабавязковая прычына стварэньня рахунку, якая будзе запісаная ў журнал.",
+ "apihelp-createaccount-param-language": "Моўны код, які будзе выстаўлены ўдзельніку па змоўчаньні (неабавязкова, па змоўчаньні мова зьместу).",
+ "apihelp-createaccount-example-pass": "Стварэньне ўдзельніка <kbd>testuser</kbd> з паролем <kbd>test123</kbd>.",
+ "apihelp-createaccount-example-mail": "Стварэньне ўдзельніка <kbd>testmailuser</kbd> і адпраўка выпадковага паролю электроннай поштай."
+}
diff --git a/includes/api/i18n/bn.json b/includes/api/i18n/bn.json
new file mode 100644
index 00000000..05407ff5
--- /dev/null
+++ b/includes/api/i18n/bn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aftabuzzaman"
+ ]
+ },
+ "apihelp-login-example-login": "প্রবেশ"
+}
diff --git a/includes/api/i18n/bs.json b/includes/api/i18n/bs.json
new file mode 100644
index 00000000..420e6ac0
--- /dev/null
+++ b/includes/api/i18n/bs.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Palapa"
+ ]
+ },
+ "apihelp-main-param-action": "Koju akciju izvesti.",
+ "apihelp-main-param-format": "Format izlaza.",
+ "apihelp-block-description": "Blokiraj korisnika",
+ "apihelp-block-param-reason": "Razlog za blokadu",
+ "apihelp-block-example-ip-simple": "Blokiraj IP 192.0.2.5 na tri dana sa razlogom \"Prvi napad\"",
+ "apihelp-compare-param-fromtitle": "Prvi naslov za poređenje.",
+ "apihelp-delete-description": "Obriši stranicu.",
+ "apihelp-edit-param-text": "Sadržaj stranice.",
+ "apihelp-edit-param-minor": "Mala izmjena."
+}
diff --git a/includes/api/i18n/ca.json b/includes/api/i18n/ca.json
new file mode 100644
index 00000000..7aa17307
--- /dev/null
+++ b/includes/api/i18n/ca.json
@@ -0,0 +1,43 @@
+{
+ "@metadata": {
+ "authors": [
+ "Toniher",
+ "Macofe",
+ "Xavier Dengra"
+ ]
+ },
+ "apihelp-main-param-format": "El format de la sortida.",
+ "apihelp-block-description": "Bloca un usuari.",
+ "apihelp-block-param-reason": "Raó del blocatge.",
+ "apihelp-block-param-nocreate": "Evita la creació de comptes.",
+ "apihelp-createaccount-description": "Creeu un nou compte d'usuari.",
+ "apihelp-createaccount-param-name": "Nom d'usuari.",
+ "apihelp-createaccount-param-password": "Contrasenya (ignorada si es defineix <var>$1mailpassword</var>)",
+ "apihelp-createaccount-param-email": "Adreça electrònica de l'usuari (opcional).",
+ "apihelp-createaccount-param-realname": "Nom real de l'usuari (opcional).",
+ "apihelp-delete-description": "Suprimeix una pàgina.",
+ "apihelp-disabled-description": "Aquest mòdul ha estat desactivat.",
+ "apihelp-edit-description": "Crea i edita pàgines.",
+ "apihelp-edit-param-text": "Contingut de la pàgina.",
+ "apihelp-edit-param-minor": "Edició menor.",
+ "apihelp-edit-param-createonly": "No editeu aquesta pàgina si ja existeix.",
+ "apihelp-edit-example-edit": "Editeu una pàgina.",
+ "apihelp-emailuser-description": "Envieu un correu electrònic a un usuari.",
+ "apihelp-emailuser-param-target": "Usuari a qui enviar el correu.",
+ "apihelp-emailuser-param-text": "Cos del correu.",
+ "apihelp-emailuser-param-ccme": "Envia'm una còpia d'aquest correu electrònic.",
+ "apihelp-expandtemplates-param-title": "Títol de la pàgina.",
+ "apihelp-feedcontributions-param-deletedonly": "Mostra només les contribucions esborrades.",
+ "apihelp-feedrecentchanges-param-hideminor": "Amaga les edicions menors.",
+ "apihelp-feedrecentchanges-param-hidebots": "Amaga les edicions de bots.",
+ "apihelp-feedrecentchanges-param-hideanons": "Amaga les edicions anònimes.",
+ "apihelp-feedrecentchanges-param-hideliu": "Amaga les edicions d'usuaris registrats.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Amaga les edicions patrullades.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Amaga les meves edicions.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filtra segons etiqueta.",
+ "apihelp-feedrecentchanges-param-target": "Mostra només els canvis de les pàgines enllaçades a aquesta pàgina.",
+ "apihelp-feedrecentchanges-example-simple": "Mostra els canvis recents.",
+ "apihelp-help-example-recursive": "Tota l'ajuda en una sola pàgina.",
+ "apihelp-import-param-rootpage": "Importa com a subpàgina d'aquesta pàgina.",
+ "apihelp-login-example-login": "Inicia sessió."
+}
diff --git a/includes/api/i18n/ce.json b/includes/api/i18n/ce.json
new file mode 100644
index 00000000..1d866ba6
--- /dev/null
+++ b/includes/api/i18n/ce.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Умар"
+ ]
+ },
+ "apihelp-main-param-action": "Кхочушдан дезарг.",
+ "apihelp-main-param-format": "Гойту формат.",
+ "apihelp-main-param-curtimestamp": "Хилламийн юкъатоха ханна йолу билгало",
+ "apihelp-createaccount-param-name": "Декъашхочун цӀе.",
+ "apihelp-userrights-param-userid": "Декъашхочун ID."
+}
diff --git a/includes/api/i18n/cs.json b/includes/api/i18n/cs.json
new file mode 100644
index 00000000..059eb82e
--- /dev/null
+++ b/includes/api/i18n/cs.json
@@ -0,0 +1,223 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mormegil",
+ "YjM",
+ "Juandev",
+ "Aktron",
+ "Cvanca"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Dokumentace]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-mailová konference]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Oznámení k API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Chyby a požadavky]\n</div>\n<strong>Stav:</strong> Všechny funkce uvedené na této stránce by měly fungovat, ale API se stále aktivně vyvíjí a může se kdykoli změnit. Upozornění na změny získáte přihlášením se k [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ e-mailové konferenci mediawiki-api-announce].\n\n<strong>Chybné požadavky:</strong> Pokud jsou do API zaslány chybné požadavky, bude vrácena HTTP hlavička s klíčem „MediaWiki-API-Error“ a hodnota této hlavičky a chybový kód budou nastaveny na stejnou hodnotu. Více informací najdete [[mw:API:Errors_and_warnings|v dokumentaci]].",
+ "apihelp-main-param-action": "Jaká akce se má provést.",
+ "apihelp-main-param-format": "Formát výstupu.",
+ "apihelp-main-param-maxlag": "Maximální zpoždění lze použít, když je MediaWiki nainstalováno na cluster s replikovanou databází. Abyste se vyhnuli zhoršování už tak špatného replikačního zpoždění, můžete tímto parametrem nechat klienta čekat, dokud replikační zpoždění neklesne pod uvedenou hodnotu. V případě příliš vysokého zpoždění se vrátí chybový kód „<samp>maxlag</samp>“ s hlášením typu „<samp>Waiting for $host: $lag seconds lagged</samp>“.<br />Více informací najdete v [[mw:Manual:Maxlag_parameter|příručce]].",
+ "apihelp-main-param-smaxage": "Nastaví hlavičku <code>s-maxage</code> na uvedený počet sekund. Chyby se nekešují nikdy.",
+ "apihelp-main-param-maxage": "Nastaví hlavičku <code>max-age</code> na uvedený počet sekund. Chyby se nekešují nikdy.",
+ "apihelp-main-param-assert": "Pokud je nastaveno na „<kbd>user</kbd>“, ověří, že je uživatel přihlášen, pokud je nastaveno na „<kbd>bot</kbd>“, ověří, že má oprávnění „bot“.",
+ "apihelp-main-param-requestid": "Libovolná zde uvedená hodnota bude zahrnuta v odpovědi. Lze použít pro rozlišení požadavků.",
+ "apihelp-main-param-servedby": "Zahrnout do odpovědi název hostitele, který požadavek obsloužil.",
+ "apihelp-main-param-curtimestamp": "Zahrnout do odpovědi aktuální časové razítko.",
+ "apihelp-main-param-origin": "Pokud k API přistupujete pomocí mezidoménového AJAXového požadavku (CORS), nastavte tento parametr na doménu původu. Musí být součástí všech předběžných požadavků, takže musí být součástí URI požadavku (nikoli těla POSTu). Hodnota musí přesně odpovídat jednomu z původů v hlavičce Origin:, takže musí být nastavena na něco jako http://en.wikipedia.org nebo https://meta.wikimedia.org. Pokud parametr neodpovídá hlavičce Origin:, bude vrácena odpověď 403. Pokud parametr odpovídá hlavičce Origin: a tento původ je na bílé listině, bude nastavena hlavička Access-Control-Allow-Origin.",
+ "apihelp-main-param-uselang": "Jazyk, který se má použít pro překlad hlášení. Seznam kódů lze načíst z <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> se <kbd>siprop=languages</kbd>, nebo zadejte „<kbd>user</kbd>“ pro použití předvoleného jazyka aktuálního uživatele či „<kbd>content</kbd>“ pro použití jazyka obsahu této wiki.",
+ "apihelp-block-description": "Zablokovat uživatele.",
+ "apihelp-block-param-user": "Uživatelské jméno, IP adresa nebo rozsah IP adres, které chcete zablokovat.",
+ "apihelp-block-param-reason": "Důvod bloku.",
+ "apihelp-block-param-anononly": "Zablokovat pouze anonymní uživatele (tj. zakázat editovat anonymně z této IP).",
+ "apihelp-block-param-nocreate": "Nedovolit registraci nových uživatelů.",
+ "apihelp-block-param-noemail": "Zakázat uživateli posílat e-maily prostřednictvím wiki. (Vyžaduje oprávnění „<code>blockemail</code>“.)",
+ "apihelp-block-param-hidename": "Skrýt uživatelské jméno v knize zablokování. (Vyžaduje oprávnění <code>hideuser</code>.)",
+ "apihelp-block-param-allowusertalk": "Povolit uživateli editovat svou vlastní diskusní stránku (závisí na <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "Pokud již uživatel blokován je, přepsat současný blok.",
+ "apihelp-block-param-watchuser": "Sledovat stránku uživatele nebo IP adresy a jejich diskuzní stránky.",
+ "apihelp-block-example-ip-simple": "Na tři dny zablokovat IP adresu <kbd>192.0.2.5</kbd> s odůvodněním <kbd>First strike</kbd>.",
+ "apihelp-block-example-user-complex": "Trvale zablokovat uživatele <kbd>Vandal</kbd> s odůvodněním <kbd>Vandalism</kbd> a zabránit vytváření nových účtů a odesílání e-mailů.",
+ "apihelp-compare-description": "Vrátí rozdíl dvou stránek.\n\nVe „from“ a „to“ musíte zadat číslo revize, název stránky nebo ID stránky.",
+ "apihelp-compare-param-fromtitle": "Název první stránky k porovnání.",
+ "apihelp-compare-param-fromid": "ID první stránky k porovnání.",
+ "apihelp-compare-param-fromrev": "Číslo revize první stránky k porovnání.",
+ "apihelp-compare-param-totitle": "Název druhé stránky k porovnání.",
+ "apihelp-compare-param-toid": "ID druhé stránky k porovnání.",
+ "apihelp-compare-param-torev": "Číslo revize druhé stránky k porovnání.",
+ "apihelp-compare-example-1": "Porovnat revize 1 a 2.",
+ "apihelp-createaccount-description": "Vytvořit nový uživatelský účet.",
+ "apihelp-createaccount-param-name": "Uživatelské jméno.",
+ "apihelp-createaccount-param-password": "Heslo (ignorováno, pokud je nastaveno <var>$1mailpassword</var>).",
+ "apihelp-createaccount-param-domain": "Doména pro externí ověření (volitelné).",
+ "apihelp-createaccount-param-email": "E-mailová adresa uživatele (nepovinné).",
+ "apihelp-createaccount-param-realname": "Skutečné jméno uživatele (nepovinné).",
+ "apihelp-createaccount-param-mailpassword": "Pokud je nastaveno na libovolnou hodnotu, zašle se náhodně vygenerované heslo na e-mail uživatele.",
+ "apihelp-createaccount-param-reason": "Případný důvod pro vytvoření účtu, který se zaznamená do logu.",
+ "apihelp-createaccount-param-language": "Kód jazyka, který se má uživateli nastavit jako výchozí (volitelné, výchozí je jazyk obsahu).",
+ "apihelp-createaccount-example-pass": "Vytvořit uživatele <kbd>testuser</kbd> s heslem <kbd>test123</kbd>.",
+ "apihelp-createaccount-example-mail": "Vytvořit uživatele <kbd>testmailuser</kbd> a zaslat mu e-mail s náhodně vygenerovaným heslem.",
+ "apihelp-delete-description": "Smazat stránku.",
+ "apihelp-delete-param-title": "Název stránky, která se má smazat. Není možné použít společně s <var>$1pageid</var>.",
+ "apihelp-delete-param-pageid": "ID stránky, která se má smazat. Není možné použít společně s <var>$1title</var>.",
+ "apihelp-delete-param-watch": "Přidat stránku na seznam sledovaných.",
+ "apihelp-delete-example-simple": "Smazat stránku <kbd>Main Page</kbd>.",
+ "apihelp-disabled-description": "Tento modul byl deaktivován.",
+ "apihelp-edit-description": "Vytvářet a upravovat stránky.",
+ "apihelp-edit-param-sectiontitle": "Název nové sekce.",
+ "apihelp-edit-param-text": "Obsah stránky.",
+ "apihelp-edit-param-minor": "Malá editace.",
+ "apihelp-edit-param-notminor": "Nemalá editace.",
+ "apihelp-edit-param-bot": "Označit tuto editaci jako editaci bota.",
+ "apihelp-edit-param-createonly": "Needitovat stránku, pokud již existuje.",
+ "apihelp-edit-param-nocreate": "Pokud stránka neexistuje, vrátit chybu.",
+ "apihelp-edit-param-watch": "Přidat stránku na seznam sledovaných.",
+ "apihelp-edit-param-unwatch": "Odstranit stránku ze seznamu sledovaných.",
+ "apihelp-edit-param-watchlist": "Bezpodmíněnečně přidat nebo odstranit stránku ze sledovaných stránek aktuálního uživatele, použít nastavení nebo neměnit sledování.",
+ "apihelp-edit-param-redirect": "Automaticky opravit přesměrování.",
+ "apihelp-edit-example-edit": "Upravit stránku.",
+ "apihelp-emailuser-description": "Poslat uživateli e-mail.",
+ "apihelp-emailuser-param-text": "Tělo zprávy.",
+ "apihelp-emailuser-param-ccme": "Odeslat mi kopii této zprávy.",
+ "apihelp-expandtemplates-param-text": "Wikitext k převedení.",
+ "apihelp-feedcontributions-description": "Vrátí kanál příspěvků uživatele.",
+ "apihelp-feedcontributions-param-feedformat": "Formát kanálu.",
+ "apihelp-feedcontributions-param-year": "Od roku (a dříve).",
+ "apihelp-feedcontributions-param-month": "Od měsíce (a dříve)",
+ "apihelp-feedcontributions-param-deletedonly": "Zobrazit pouze smazané příspěvky.",
+ "apihelp-feedrecentchanges-param-namespace": "Jmenný prostor, na který mají být výsledky omezeny.",
+ "apihelp-feedrecentchanges-param-from": "Zobrazit změny od",
+ "apihelp-feedrecentchanges-param-hideminor": "Skrýt drobné změny.",
+ "apihelp-feedrecentchanges-param-hidebots": "Skrýt úpravy provedené roboty.",
+ "apihelp-feedrecentchanges-param-hideanons": "Skrýt změny provedené anonymními uživateli.",
+ "apihelp-feedrecentchanges-param-hideliu": "Skrýt změny provedené registrovanými uživateli.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Skrýt prověřené změny.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Skrýt změny aktuálního uživatele.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filtrovat podle značek.",
+ "apihelp-feedrecentchanges-param-target": "Zobrazit jen změny na stránkách odkazovaných z této stránky.",
+ "apihelp-feedrecentchanges-example-simple": "Zobrazit poslední změny.",
+ "apihelp-feedrecentchanges-example-30days": "Zobrazit poslední změny za 30 dní.",
+ "apihelp-filerevert-description": "Revertovat soubor na starší verzi.",
+ "apihelp-filerevert-param-filename": "Cílový název souboru, bez prefixu Soubor:",
+ "apihelp-filerevert-param-comment": "Vložit komentář.",
+ "apihelp-help-description": "Zobrazuje nápovědu k uvedeným modulům.",
+ "apihelp-help-param-modules": "Moduly, pro které se má zobrazit nápověda (hodnoty parametrů action= a format= nebo „main“). Submoduly lze zadávat pomocí „+“.",
+ "apihelp-help-param-submodules": "Zahrnout nápovědu pro podmoduly uvedeného modulu.",
+ "apihelp-help-param-recursivesubmodules": "Zahrnout nápovědu pro podmoduly rekurzivně.",
+ "apihelp-help-param-helpformat": "Formát výstupu nápovědy.",
+ "apihelp-help-param-wrap": "Obalit výstup do standardní struktury API odpovědi.",
+ "apihelp-help-param-toc": "Zahrnout v HTML výstupu tabulku obsahu.",
+ "apihelp-help-example-main": "Nápověda k hlavnímu modulu",
+ "apihelp-help-example-recursive": "Veškerá nápověda na jedné stránce",
+ "apihelp-help-example-help": "Nápověda k samotnému modulu nápovědy",
+ "apihelp-help-example-query": "Nápověda pro dva podmoduly query",
+ "apihelp-imagerotate-description": "Otočit jeden nebo více obrázků.",
+ "apihelp-import-param-summary": "Import shrnutí.",
+ "apihelp-import-param-xml": "Nahraný XML soubor.",
+ "apihelp-import-param-rootpage": "Importovat jako podstránku k této stránce.",
+ "apihelp-login-param-name": "Uživatelské jméno.",
+ "apihelp-login-param-password": "Heslo.",
+ "apihelp-login-param-domain": "Doména (volitelná)",
+ "apihelp-login-example-login": "Přihlášení",
+ "apihelp-logout-example-logout": "Odhlášení aktuálního uživatele.",
+ "apihelp-move-description": "Přesunout stránku.",
+ "apihelp-move-param-reason": "Důvod k přejmenování.",
+ "apihelp-move-param-movetalk": "Přejmenovat diskuzní stránku, pokud existuje.",
+ "apihelp-move-param-movesubpages": "Přejmenovat možné podstránky",
+ "apihelp-move-param-noredirect": "Nevytvářet přesměrování.",
+ "apihelp-move-param-watch": "Přidat stránku a přesměrování do sledovaných stránek aktuálního uživatele.",
+ "apihelp-move-param-unwatch": "Odstranit stránku a přesměrování ze sledovaných stránek současného uživatele.",
+ "apihelp-move-param-ignorewarnings": "Ignorovat všechna varování.",
+ "apihelp-opensearch-param-search": "Hledaný řetězec.",
+ "apihelp-opensearch-param-limit": "Maximální počet vrácených výsledků",
+ "apihelp-opensearch-param-namespace": "Jmenné prostory pro vyhledávání.",
+ "apihelp-opensearch-param-format": "Formát výstupu.",
+ "apihelp-opensearch-example-te": "Najít stránky začínající na „<kbd>Te</kbd>“.",
+ "apihelp-options-example-reset": "Vrátit všechna nastavení.",
+ "apihelp-parse-example-page": "Parsovat stránku.",
+ "apihelp-parse-example-text": "Parsovat wikitext.",
+ "apihelp-patrol-example-revid": "Prověřit revizi.",
+ "apihelp-protect-description": "Změnit úroveň zamčení stránky.",
+ "apihelp-protect-param-reason": "Důvod pro odemčení.",
+ "apihelp-protect-example-protect": "Zamknout stránku.",
+ "apihelp-query+alldeletedrevisions-description": "Seznam všech smazaných revizí od konkrétního uživatele nebo v konkrétním jmenném prostoru.",
+ "apihelp-query+alldeletedrevisions-example-user": "Seznam posledních 50 smazaných editací uživatele <kbd>Příklad<kbd>.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Seznam prvních 50 smazaných revizí v hlavním jmenném prostoru.",
+ "apihelp-query+allfileusages-description": "Zobrazit seznam všech použití souboru, včetně neexistujících.",
+ "apihelp-query+allfileusages-example-unique": "Zobrazit seznam unikátních názvů souborů.",
+ "apihelp-query+alllinks-example-generator": "Získat stránky obsahující odkazy.",
+ "apihelp-query+allpages-param-filterredir": "Které stránky uvést na seznam.",
+ "apihelp-query+allpages-param-minsize": "Omezit na stránky s určitým počtem bajtů.",
+ "apihelp-query+allpages-param-prtype": "Omezit jen na zamčené stránky.",
+ "apihelp-query+allpages-example-B": "Zobrazit seznam stránek začínajících na písmeno <kbd>B</kbd>.",
+ "apihelp-query+allredirects-description": "Seznam všech přesměrování pro jmenný prostor.",
+ "apihelp-query+allredirects-example-unique": "Seznam unikátních cílových stránek.",
+ "apihelp-query+allredirects-example-generator": "Získat stránky obsahující přesměrování.",
+ "apihelp-query+alltransclusions-example-unique": "Seznam unikátně vložených titulů.",
+ "apihelp-query+backlinks-example-simple": "Zobrazit odkazy na <kbd>Hlavní stránka</kbd>",
+ "apihelp-query+categorymembers-description": "Seznam všech stránek v dané kategorii.",
+ "apihelp-query+deletedrevisions-param-limit": "Maximální počet revizí pro zobrazení v seznamu.",
+ "apihelp-query+embeddedin-example-simple": "Zobrazit stránky, které obahují <kbd>Template:Stub</kbd>.",
+ "apihelp-query+filearchive-example-simple": "Zobrazit seznam všech smazaných souborů.",
+ "apihelp-query+filerepoinfo-example-simple": "Získat informace o souborových repozitářích.",
+ "apihelp-query+linkshere-example-generator": "Získat informace o stránkách, které odkazují na [[Hlavní Stránka|Hlavní stránku]].",
+ "apihelp-query+recentchanges-example-simple": "Seznam posledních změn.",
+ "apihelp-query+tags-example-simple": "Získat seznam dostupných tagů.",
+ "apihelp-query+usercontribs-example-user": "Zobrazit příspěvky uživatele <kbd>Příklad</kbd>",
+ "apihelp-query+watchlistraw-description": "Získat všechny stránky, které jsou aktuálním uživatelem sledovány.",
+ "apihelp-query+watchlistraw-example-simple": "Seznam sledovaných stránek uživatele.",
+ "apihelp-unblock-param-user": "Uživatel, IP adresa nebo záběr IP adres k odblokování. Nelze použít dohromady s <var>$1id</var>.",
+ "apihelp-watch-example-watch": "Sledovat stránku <kbd>Hlavní stránka</kbd>.",
+ "apihelp-watch-example-generator": "Zobrazit prvních několik stránek z hlavního jmenného prostoru.",
+ "apihelp-format-example-generic": "Výsledek dotazu vypsat ve formátu $1.",
+ "apihelp-dbg-description": "Vypisuje data ve formátu funkce var_export() z PHP.",
+ "apihelp-dbgfm-description": "Vypisuje data ve formátu funkce var_export() z PHP (v čitelné HTML podobě).",
+ "apihelp-dump-description": "Vypisuje data ve formátu funkce var_dump() z PHP.",
+ "apihelp-dumpfm-description": "Vypisuje data ve formátu funkce var_dump() z PHP (v čitelné HTML podobě).",
+ "apihelp-json-description": "Vypisuje data ve formátu JSON.",
+ "apihelp-json-param-callback": "Pokud je uvedeno, obalí výstup do zadaného volání funkce. Z bezpečnostních důvodů budou omezena všechna data specifická pro uživatele.",
+ "apihelp-json-param-utf8": "Pokud je uvedeno, bude většina ne-ASCII znaků (ale ne všechny) kódována v UTF-8 místo nahrazení hexadecimálními escape sekvencemi.",
+ "apihelp-jsonfm-description": "Vypisuje data ve formátu JSON (v čitelné HTML podobě).",
+ "apihelp-none-description": "Nevypisuje nic.",
+ "apihelp-php-description": "Vypisuje data v serializačním formátu PHP.",
+ "apihelp-phpfm-description": "Vypisuje data v serializačním formátu PHP (v čitelné HTML podobě).",
+ "apihelp-rawfm-description": "Vypisuje data s ladicími prvky ve formátu JSON (v čitelné HTML podobě).",
+ "apihelp-txt-description": "Vypisuje data ve formátu funkce print_r() z PHP.",
+ "apihelp-txtfm-description": "Vypisuje data ve formátu funkce print_r() z PHP (v čitelné HTML podobě).",
+ "apihelp-wddx-description": "Vypisuje data ve formátu WDDX.",
+ "apihelp-wddxfm-description": "Vypisuje data ve formátu WDDX (v čitelné HTML podobě).",
+ "apihelp-xml-description": "Vypisuje data ve formátu XML.",
+ "apihelp-xml-param-xslt": "Pokud je uvedeno, přidá stylopis &lt;xslt&gt;. Měla by jím být wikistránka v jmenném prostoru MediaWiki, jejíž název končí na „.xsl“.",
+ "apihelp-xml-param-includexmlnamespace": "Pokud je uvedeno, přidá jmenný prostor XML.",
+ "apihelp-xmlfm-description": "Vypisuje data ve formátu XML (v čitelné HTML podobě).",
+ "apihelp-yaml-description": "Vypisuje data ve formátu YAML.",
+ "apihelp-yamlfm-description": "Vypisuje data ve formátu YAML (v čitelné HTML podobě).",
+ "api-format-title": "Odpověď z MediaWiki API",
+ "api-format-prettyprint-header": "Díváte se na HTML reprezentaci formátu $1. HTML se hodí pro ladění, ale pro aplikační použití je nevhodné.\n\nPro změnu výstupního formátu uveďte parametr format. Abyste viděli ne-HTML reprezentaci formátu $1, nastavte format=$2.\n\nVíce informací najdete v [https://www.mediawiki.org/wiki/Special:MyLanguage/API:Main_page úplné dokumentaci] nebo [[Special:ApiHelp/main|nápovědě k API]].",
+ "api-help-title": "Nápověda k MediaWiki API",
+ "api-help-lead": "Toto je automaticky generovaná dokumentační stránka k MediaWiki API.\n\nDokumentace a příklady: https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "Hlavní modul",
+ "api-help-flag-deprecated": "Tento modul je zastaralý.",
+ "api-help-flag-internal": "<strong>Tento modul je interní nebo nestabilní.</strong> Jeho funkčnost se může bez předchozího upozornění změnit.",
+ "api-help-flag-readrights": "Tento modul vyžaduje oprávnění ke čtení.",
+ "api-help-flag-writerights": "Tento modul vyžaduje oprávnění k zápisu.",
+ "api-help-flag-mustbeposted": "Tento modul přijímá pouze požadavky POST.",
+ "api-help-flag-generator": "Tento modul lze využívat jako generátor.",
+ "api-help-parameters": "{{PLURAL:$1|Parametr|Parametry}}:",
+ "api-help-param-deprecated": "Zastaralý.",
+ "api-help-param-required": "Tento parametr je povinný.",
+ "api-help-param-list": "{{PLURAL:$1|1=Jedna hodnota|2=Hodnoty (oddělené „{{!}}“)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Musí být prázdné|Může být prázdné nebo $2}}",
+ "api-help-param-limit": "Není dovoleno více než $1.",
+ "api-help-param-limit2": "Není dovoleno více než $1 ($2 pro boty).",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=Hodnota nesmí|2=Hodnoty nesmějí}} být nižší než $2.",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=Hodnota nesmí|2=Hodnoty nesmějí}} být vyšší než $3.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=Hodnota|2=Hodnoty}} musí ležet mezi $2 a $3.",
+ "api-help-param-upload": "Musí se odeslat POST požadavkem jako načítaný soubor pomocí multipart/form-data.",
+ "api-help-param-multi-separate": "Hodnoty oddělujte pomocí „|“.",
+ "api-help-param-multi-max": "Maximální počet hodnot je {{PLURAL:$1|$1}} (pro boty {{PLURAL:$2|$2}}).",
+ "api-help-param-default": "Implicitní hodnota: $1",
+ "api-help-param-default-empty": "Implicitní hodnota: <span class=\"apihelp-empty\">(prázdné)</span>",
+ "api-help-param-token": "Token typu „$1“ získaný pomocí [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(bez popisu)</span>",
+ "api-help-examples": "{{PLURAL:$1|Příklad|Příklady}}:",
+ "api-help-permissions": "{{PLURAL:$1|Oprávnění}}:",
+ "api-help-permissions-granted-to": "Uděleno {{PLURAL:$1|skupině|skupinám}}: $2",
+ "api-help-right-apihighlimits": "Používání vyšších limitů v API dotazech (pomalé dotazy: $1, rychlé dotazy: $2). Limity pro pomalé dotazy se vztahují i na vícehodnotové parametry.",
+ "api-credits-header": "Zásluhy",
+ "api-credits": "Vývojáři API:\n* Roan Kattouw (hlavní vývojář září 2007–2009)\n* Viktor Vasiljev\n* Bryan Tong Minh\n* Sam Reed\n* Jurij Astrachan (tvůrce, hlavní vývojář září 2006–září 2007)\n* Brad Jorsch (hlavní vývojář od 2013)\n\nSvé komentáře, návrhy či dotazy posílejte na mediawiki-api@lists.wikimedia.org\nnebo založte chybové hlášení na https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/cv.json b/includes/api/i18n/cv.json
new file mode 100644
index 00000000..88f222f8
--- /dev/null
+++ b/includes/api/i18n/cv.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chuvash2014"
+ ]
+ },
+ "apihelp-login-example-login": "Кĕр"
+}
diff --git a/includes/api/i18n/de.json b/includes/api/i18n/de.json
new file mode 100644
index 00000000..96172040
--- /dev/null
+++ b/includes/api/i18n/de.json
@@ -0,0 +1,433 @@
+{
+ "@metadata": {
+ "authors": [
+ "Florian",
+ "Kghbln",
+ "Metalhead64",
+ "Inkowik",
+ "Umherirrender",
+ "Giftpflanze",
+ "Macofe",
+ "Se4598",
+ "Purodha"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page/de|Dokumentation]]\n* [[mw:API:FAQ/de|Häufig gestellte Fragen]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Mailingliste]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-Ankündigungen]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Fehlerberichte und Anfragen]\n</div>\n<strong>Status:</strong> Alle auf dieser Seite gezeigten Funktionen sollten funktionieren, allerdings ist die API in aktiver Entwicklung und kann sich zu jeder Zeit ändern. Abonniere die [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ MediaWiki-API-Ankündigungs-Mailingliste], um über Aktualisierungen informiert zu werden.\n\n<strong>Fehlerhafte Anfragen:</strong> Wenn fehlerhafte Anfragen an die API gesendet werden, wird ein HTTP-Header mit dem Schlüssel „MediaWiki-API-Error“ gesendet. Der Wert des Headers und der Fehlercode werden auf den gleichen Wert gesetzt. Für weitere Informationen siehe [[mw:API:Errors_and_warnings|API: Fehler und Warnungen]].",
+ "apihelp-main-param-action": "Auszuführende Aktion.",
+ "apihelp-main-param-format": "Format der Ausgabe.",
+ "apihelp-main-param-maxlag": "maxlag kann verwendet werden, wenn MediaWiki auf einem datenbankreplizierten Cluster installiert ist. Um weitere Replikationsrückstände zu verhindern, lässt dieser Parameter den Client warten, bis der Replikationsrückstand kleiner als der angegebene Wert (in Sekunden) ist. Bei einem größerem Rückstand wird der Fehlercode <samp>maxlag</samp> zurückgegeben mit einer Nachricht wie <samp>Waiting for $host: $lag seconds lagged</samp>.<br />Siehe [[mw:Manual:Maxlag_parameter|Handbuch: Maxlag parameter]] für weitere Informationen.",
+ "apihelp-main-param-smaxage": "Den <code>s-maxage</code>-Header auf diese Anzahl Sekunden festlegen. Fehler werden niemals gecacht.",
+ "apihelp-main-param-maxage": "Den <code>max-age</code>-Header auf diese Anzahl Sekunden festlegen. Fehler werden niemals gecacht.",
+ "apihelp-main-param-assert": "Sicherstellen, dass der Benutzer eingeloggt ist, wenn auf <kbd>user</kbd> gesetzt, oder Bot ist, wenn auf <kbd>bot</kbd> gesetzt.",
+ "apihelp-main-param-requestid": "Der angegebene Wert wird mit in die Antwort aufgenommen und kann zur Unterscheidung von Anfragen verwendet werden.",
+ "apihelp-main-param-servedby": "Namen des bearbeitenden Hosts mit zurückgeben.",
+ "apihelp-main-param-curtimestamp": "Aktuellen Zeitstempel mit zurückgeben.",
+ "apihelp-main-param-origin": "Beim Zugriff auf die API mittels Cross-Domain-AJAX-Anfrage (CORS) ist dieser Parameter auf die veranlassende Domain zu setzen. Er muss in jedem Pre-Flight-Request angegeben werden und deshalb ein Teil der Anfrage-URI sein (nicht des POST-Bodys). Er muss genau einer der Angaben im <code>Origin</code>-Header entsprechen, d.&nbsp;h. er muss auf etwas wie <kbd>https://de.wikipedia.org</kbd> oder <kbd>https://meta.wikimedia.org</kbd> gesetzt werden. Falls dieser Parameter nicht mit dem <code>Origin</code>-Header übereinstimmt, wird eine 403-Antwort zurückgegeben. Falls dieser Parameter dem <code>Origin</code>-Header entspricht und die Domain auf der Whitelist ist, wird ein <code>Access-Control-Allow-Origin</code>-Header gesetzt.",
+ "apihelp-main-param-uselang": "Zu verwendende Sprache für Nachrichtenübersetzungen. Eine Liste der Codes kann von <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> mit <kbd>siprop=languages</kbd> abgerufen werden. Gib <kbd>user</kbd> zum Verwenden der aktuellen Benutzerspracheinstellung oder <kbd>content</kbd> an, um die Inhaltssprache des Wikis zu verwenden.",
+ "apihelp-block-description": "Einen Benutzer sperren.",
+ "apihelp-block-param-user": "Benutzername, IP-Adresse oder IP-Bereich, der gesperrt werden soll.",
+ "apihelp-block-param-expiry": "Sperrdauer. Kann relativ (z.&nbsp;B. <kbd>5 months</kbd> oder <kbd>2 weeks</kbd>) oder absolut (z.&nbsp;B. <kbd>2014-09-18T12:34:56Z</kbd>) sein. Wenn auf <kbd>infinite</kbd>, <kbd>indefinite</kbd> oder <kbd>never</kbd> gesetzt, ist die Sperre unbegrenzt.",
+ "apihelp-block-param-reason": "Sperrbegründung.",
+ "apihelp-block-param-anononly": "Nur anonyme Benutzer sperren (z.&nbsp;B. anonyme Bearbeitungen für diese IP deaktivieren).",
+ "apihelp-block-param-nocreate": "Benutzerkontenerstellung verhindern.",
+ "apihelp-block-param-autoblock": "Die zuletzt verwendete IP-Adresse automatisch sperren und alle darauffolgenden IP-Adressen, die versuchen sich anzumelden.",
+ "apihelp-block-param-noemail": "Benutzer davon abhalten, E-Mails auf dem Wiki zu versenden (erfordert das <code>blockemail</code>-Recht).",
+ "apihelp-block-param-hidename": "Den Benutzernamen im Sperr-Logbuch verstecken (erfordert das <code>hideuser</code>-Recht).",
+ "apihelp-block-param-allowusertalk": "Dem Benutzer erlauben, seine eigene Diskussionsseite zu bearbeiten (abhängig von <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "Falls der Benutzer bereits gesperrt ist, die vorhandene Sperre überschreiben.",
+ "apihelp-block-param-watchuser": "Benutzer- und Diskussionsseiten des Benutzers oder der IP-Adresse beobachten.",
+ "apihelp-block-example-ip-simple": "IP <kbd>192.0.2.5</kbd> für drei Tage mit der Begründung „First strike“ (erste Verwarnung) sperren",
+ "apihelp-block-example-user-complex": "Benutzer <kbd>Vandal</kbd> unbeschränkt sperren mit der Begründung „Vandalism“ (Vandalismus), Erstellung neuer Benutzerkonten sowie Versand von E-Mails verhindern.",
+ "apihelp-checktoken-description": "Überprüft die Gültigkeit eines über <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> erhaltenen Tokens.",
+ "apihelp-checktoken-param-type": "Typ des Tokens, das getestet werden soll.",
+ "apihelp-checktoken-param-token": "Token, das getestet werden soll.",
+ "apihelp-checktoken-param-maxtokenage": "Maximal erlaubtes Alter des Tokens in Sekunden.",
+ "apihelp-checktoken-example-simple": "Überprüft die Gültigkeit des <kbd>csrf</kbd>-Tokens.",
+ "apihelp-clearhasmsg-description": "Löschen des <code>hasmsg</code>-Flags („hat Nachrichten“-Flag) für den aktuellen Benutzer.",
+ "apihelp-clearhasmsg-example-1": "<code>hasmsg</code>-Flags für den aktuellen Benutzer löschen",
+ "apihelp-compare-description": "Abrufen des Unterschieds zwischen zwei Seiten.\n\nDu musst eine Versionsnummer, einen Seitentitel oder eine Seitennummer für „from“ als auch „to“ angeben.",
+ "apihelp-compare-param-fromtitle": "Erster zu vergleichender Titel.",
+ "apihelp-compare-param-fromid": "Erste zu vergleichende Seitennummer.",
+ "apihelp-compare-param-fromrev": "Erste zu vergleichende Version.",
+ "apihelp-compare-param-totitle": "Zweiter zu vergleichender Titel.",
+ "apihelp-compare-param-toid": "Zweite zu vergleichende Seitennummer.",
+ "apihelp-compare-param-torev": "Zweite zu vergleichende Version.",
+ "apihelp-compare-example-1": "Unterschied zwischen Version 1 und 2 abrufen",
+ "apihelp-createaccount-description": "Erstellen eines neuen Benutzerkontos.",
+ "apihelp-createaccount-param-name": "Benutzername.",
+ "apihelp-createaccount-param-password": "Passwort (wird ignoriert, wenn <var>$1mailpassword</var> angegeben ist).",
+ "apihelp-createaccount-param-domain": "Domain für die externe Authentifizierung (optional).",
+ "apihelp-createaccount-param-token": "Der in der ersten Anfrage erhaltene Benutzerkontenerstellungs-Token.",
+ "apihelp-createaccount-param-email": "E-Mail-Adresse des Benutzers (optional).",
+ "apihelp-createaccount-param-realname": "Realname des Benutzers (optional).",
+ "apihelp-createaccount-param-mailpassword": "Wenn ein Wert angegeben wird, wird ein zufälliges Passwort per E-Mail an den Benutzer versandt.",
+ "apihelp-createaccount-param-reason": "Optionale Begründung für die Benutzerkontenerstellung, die in den Logbüchern vermerkt wird.",
+ "apihelp-createaccount-param-language": "Festzulegender standardmäßiger Sprachcode für den Benutzer (optional, Standard ist Inhaltssprache).",
+ "apihelp-createaccount-example-pass": "Benutzer <kbd>testuser</kbd> mit dem Passwort <kbd>test123</kbd> erstellen.",
+ "apihelp-createaccount-example-mail": "Benutzer <kbd>testmailuser</kbd> erstellen und zufällig generiertes Passwort per E-Mail verschicken.",
+ "apihelp-delete-description": "Löschen einer Seite.",
+ "apihelp-delete-param-title": "Titel der Seite, die gelöscht werden soll. Kann nicht zusammen mit <var>$1pageid</var> verwendet werden.",
+ "apihelp-delete-param-pageid": "Seitennummer der Seite, die gelöscht werden soll. Kann nicht zusammen mit <var>$1title</var> verwendet werden.",
+ "apihelp-delete-param-reason": "Löschbegründung. Falls nicht festgelegt, wird eine automatisch generierte Begründung verwendet.",
+ "apihelp-delete-param-watch": "Seite auf die Beobachtungsliste des aktuellen Benutzers setzen.",
+ "apihelp-delete-param-watchlist": "Seite zur Beobachtungsliste des aktuellen Benutzers hinzufügen oder von ihr entfernen, die Standardeinstellungen verwenden oder die Beobachtung nicht ändern.",
+ "apihelp-delete-param-unwatch": "Seite von der Beobachtungsliste entfernen.",
+ "apihelp-delete-param-oldimage": "Name des alten zu löschenden Bildes, wie von [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]] angegeben.",
+ "apihelp-delete-example-simple": "<kbd>Hauptseite</kbd> löschen.",
+ "apihelp-delete-example-reason": "<kbd>Hauptseite</kbd> löschen mit der Begründung <kbd>Vorbereitung für Verschiebung</kbd>.",
+ "apihelp-disabled-description": "Dieses Modul wurde deaktiviert.",
+ "apihelp-edit-description": "Erstellen und Bearbeiten von Seiten.",
+ "apihelp-edit-param-title": "Titel der Seite, die bearbeitet werden soll. Kann nicht zusammen mit <var>$1pageid</var> verwendet werden.",
+ "apihelp-edit-param-pageid": "Seitennummer der Seite, die bearbeitet werden soll. Kann nicht zusammen mit <var>$1title</var> verwendet werden.",
+ "apihelp-edit-param-section": "Abschnittsnummer. <kbd>0</kbd> für die Einleitung, <kbd>new</kbd> für einen neuen Abschnitt.",
+ "apihelp-edit-param-sectiontitle": "Die Überschrift für einen neuen Abschnitt.",
+ "apihelp-edit-param-text": "Seiteninhalt.",
+ "apihelp-edit-param-summary": "Bearbeitungszusammenfassung. Auch Abschnittsüberschrift, wenn $1section=new und $1sectiontitle nicht festgelegt ist.",
+ "apihelp-edit-param-minor": "Kleine Bearbeitung.",
+ "apihelp-edit-param-notminor": "Nicht-kleine Bearbeitung.",
+ "apihelp-edit-param-bot": "Diese Bearbeitung als Bot-Bearbeitung markieren.",
+ "apihelp-edit-param-basetimestamp": "Zeitstempel der Basisversion, wird verwendet zum Aufspüren von Bearbeitungskonflikten. Kann abgerufen werden durch [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
+ "apihelp-edit-param-starttimestamp": "Zeitstempel, an dem der Bearbeitungsprozess begonnen wurde. Er wird zum Aufspüren von Bearbeitungskonflikten verwendet. Ein geeigneter Wert kann mithilfe von <var>[[Special:ApiHelp/main|curtimestamp]]</var> beim Beginn des Bearbeitungsprozesses (z.&nbsp;B. beim Laden des Seiteninhalts zum Bearbeiten) abgerufen werden.",
+ "apihelp-edit-param-recreate": "Keinen Fehler zurückgeben, wenn die Seite in der Zwischenzeit gelöscht wurde.",
+ "apihelp-edit-param-createonly": "Seite nicht bearbeiten, falls sie bereits vorhanden ist.",
+ "apihelp-edit-param-nocreate": "Einen Fehler zurückgeben, falls die Seite nicht vorhanden ist.",
+ "apihelp-edit-param-watch": "Seite der Beobachtungsliste hinzufügen.",
+ "apihelp-edit-param-unwatch": "Seite von der Beobachtungsliste entfernen.",
+ "apihelp-edit-param-watchlist": "Die Seite zur Beobachtungsliste des aktuellen Benutzers hinzufügen oder von ihr entfernen, die Standardeinstellungen verwenden oder die Beobachtung nicht ändern.",
+ "apihelp-edit-param-md5": "Der MD5-Hash des Parameters $1text oder der aneinandergehängten Parameter $1prependtext und $1appendtext. Wenn angegeben, wird die Bearbeitung nicht ausgeführt, wenn der Hash nicht korrekt ist.",
+ "apihelp-edit-param-prependtext": "Diesen Text an den Anfang der Seite setzen. Überschreibt $1text.",
+ "apihelp-edit-param-appendtext": "Diesen Text an das Ende der Seite hinzufügen. Überschreibt $1text.\n\nVerwende statt dieses Parameters $1section=new zum Anhängen eines neuen Abschnitts.",
+ "apihelp-edit-param-undo": "Diese Version rückgängig machen. Überschreibt $1text, $1prependtext und $1appendtext.",
+ "apihelp-edit-param-undoafter": "Alle Versionen von $1undo bis zu dieser rückgängig machen. Falls nicht angegeben, nur eine Version rückgängig machen.",
+ "apihelp-edit-param-redirect": "Weiterleitungen automatisch auflösen.",
+ "apihelp-edit-param-contentformat": "Für den Eingabetext verwendetes Inhaltsserialisierungsformat.",
+ "apihelp-edit-param-contentmodel": "Inhaltsmodell des neuen Inhalts.",
+ "apihelp-edit-param-token": "Der Token sollte immer als letzter Parameter gesendet werden, zumindest aber nach dem $1text-Parameter.",
+ "apihelp-edit-example-edit": "Eine Seite bearbeiten",
+ "apihelp-edit-example-prepend": "<kbd>_&#95;NOTOC_&#95;</kbd> bei einer Seite voranstellen",
+ "apihelp-edit-example-undo": "Versionen 13579 bis 13585 mit automatischer Zusammenfassung rückgängig machen",
+ "apihelp-emailuser-description": "E-Mail an einen Benutzer senden.",
+ "apihelp-emailuser-param-target": "Benutzer, an den die E-Mail gesendet werden soll.",
+ "apihelp-emailuser-param-subject": "Betreffzeile.",
+ "apihelp-emailuser-param-text": "E-Mail-Inhalt.",
+ "apihelp-emailuser-param-ccme": "Eine Kopie dieser E-Mail an mich senden.",
+ "apihelp-emailuser-example-email": "Eine E-Mail an den Benutzer <kbd>WikiSysop</kbd> mit dem Text <kbd>Inhalt</kbd> senden.",
+ "apihelp-expandtemplates-description": "Alle Vorlagen im Wikitext expandieren.",
+ "apihelp-expandtemplates-param-title": "Titel der Seite.",
+ "apihelp-expandtemplates-param-text": "Zu konvertierender Wikitext.",
+ "apihelp-expandtemplates-param-revid": "Versionsnummer, die für die Anzeige von <nowiki>{{REVISIONID}}</nowiki> und ähnlichen Variablen verwendet wird.",
+ "apihelp-expandtemplates-param-includecomments": "Ob HTML-Kommentare in der Ausgabe eingeschlossen werden sollen.",
+ "apihelp-expandtemplates-param-generatexml": "XML-Parserbaum erzeugen (ersetzt durch $1prop=parsetree).",
+ "apihelp-expandtemplates-example-simple": "Den Wikitext <kbd><nowiki>{{Project:Spielwiese}}</nowiki></kbd> expandieren.",
+ "apihelp-feedcontributions-description": "Gibt einen Benutzerbeiträge-Feed zurück.",
+ "apihelp-feedcontributions-param-feedformat": "Das Format des Feeds.",
+ "apihelp-feedcontributions-param-user": "Von welchen Benutzern die Beiträge abgerufen werden sollen.",
+ "apihelp-feedcontributions-param-namespace": "Auf welchen Namensraum die Beiträge begrenzt werden sollen.",
+ "apihelp-feedcontributions-param-year": "Von Jahr (und früher).",
+ "apihelp-feedcontributions-param-month": "Von Monat (und früher).",
+ "apihelp-feedcontributions-param-tagfilter": "Beiträge filtern, die diese Markierungen haben.",
+ "apihelp-feedcontributions-param-deletedonly": "Nur gelöschte Beiträge anzeigen.",
+ "apihelp-feedcontributions-param-toponly": "Nur aktuelle Versionen anzeigen.",
+ "apihelp-feedcontributions-param-newonly": "Nur Seitenerstellungen anzeigen.",
+ "apihelp-feedcontributions-param-showsizediff": "Zeigt den Größenunterschied zwischen Versionen an.",
+ "apihelp-feedcontributions-example-simple": "Beiträge für die Benutzer <kbd>Beispiel<kbd> zurückgeben",
+ "apihelp-feedrecentchanges-description": "Gibt einen Letzte-Änderungen-Feed zurück.",
+ "apihelp-feedrecentchanges-param-feedformat": "Das Format des Feeds.",
+ "apihelp-feedrecentchanges-param-namespace": "Namensraum, auf den die Ergebnisse beschränkt werden sollen.",
+ "apihelp-feedrecentchanges-param-invert": "Alle Namensräume außer dem ausgewählten.",
+ "apihelp-feedrecentchanges-param-associated": "Verbundenen Namensraum (Diskussions oder Hauptnamensraum) mit einschließen.",
+ "apihelp-feedrecentchanges-param-days": "Tage, auf die die Ergebnisse beschränkt werden sollen.",
+ "apihelp-feedrecentchanges-param-limit": "Maximale Anzahl zurückzugebender Ergebnisse.",
+ "apihelp-feedrecentchanges-param-from": "Änderungen seit jetzt anzeigen.",
+ "apihelp-feedrecentchanges-param-hideminor": "Kleine Änderungen ausblenden.",
+ "apihelp-feedrecentchanges-param-hidebots": "Änderungen von Bots ausblenden.",
+ "apihelp-feedrecentchanges-param-hideanons": "Änderungen von anonymen Benutzern ausblenden.",
+ "apihelp-feedrecentchanges-param-hideliu": "Änderungen von registrierten Benutzern ausblenden.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Kontrollierte Änderungen ausblenden.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Änderungen des aktuellen Benutzers ausblenden.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Nach Markierung filtern.",
+ "apihelp-feedrecentchanges-param-target": "Nur Änderungen an Seiten anzeigen, die von dieser Seite verlinkt sind.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Zeige Änderungen an Seiten die von der ausgewählten Seite verlinkt sind.",
+ "apihelp-feedrecentchanges-example-simple": "Letzte Änderungen anzeigen",
+ "apihelp-feedrecentchanges-example-30days": "Letzte Änderungen für 30 Tage anzeigen",
+ "apihelp-feedwatchlist-description": "Gibt einen Beobachtungslisten-Feed zurück.",
+ "apihelp-feedwatchlist-param-feedformat": "Das Format des Feeds.",
+ "apihelp-feedwatchlist-param-hours": "Seiten auflisten, die innerhalb dieser Anzahl Stunden ab jetzt geändert wurden.",
+ "apihelp-feedwatchlist-param-linktosections": "Verlinke direkt zum veränderten Abschnitt, wenn möglich.",
+ "apihelp-feedwatchlist-example-default": "Den Beobachtungslisten-Feed anzeigen",
+ "apihelp-feedwatchlist-example-all6hrs": "Zeige alle Änderungen an beobachteten Seiten der letzten 6 Stunden.",
+ "apihelp-filerevert-description": "Eine Datei auf eine alte Version zurücksetzen.",
+ "apihelp-filerevert-param-filename": "Ziel-Datei, ohne das Datei:-Präfix.",
+ "apihelp-filerevert-param-comment": "Hochladekommentar.",
+ "apihelp-filerevert-param-archivename": "Archivname der Version, auf die die Datei zurückgesetzt werden soll.",
+ "apihelp-filerevert-example-revert": "<kbd>Wiki.png</kbd> auf die Version vom <kbd>2011-03-05T15:27:40Z</kbd> zurücksetzen",
+ "apihelp-help-description": "Hilfe für die angegebenen Module anzeigen.",
+ "apihelp-help-param-modules": "Module, zu denen eine Hilfe angezeigt werden soll (Werte der Parameter <var>action</var> und <var>format</var> oder <kbd>main</kbd>). Kann Submodule mit einem <kbd>+</kbd> angeben.",
+ "apihelp-help-param-submodules": "Hilfe für Submodule des benannten Moduls einschließen.",
+ "apihelp-help-param-recursivesubmodules": "Hilfe für Submodule rekursiv einschließen.",
+ "apihelp-help-param-helpformat": "Format der Hilfe-Ausgabe.",
+ "apihelp-help-param-wrap": "Die Ausgabe in eine Standard-API-Antwort-Struktur einschließen.",
+ "apihelp-help-param-toc": "Ein Inhaltsverzeichnis in der HTML-Ausgabe einschließen.",
+ "apihelp-help-example-main": "Hilfe für das Hauptmodul",
+ "apihelp-help-example-recursive": "Alle Hilfen in einer Seite",
+ "apihelp-help-example-help": "Hilfe für das Hilfemodul selbst",
+ "apihelp-help-example-query": "Hilfe für zwei Abfrage-Submodule",
+ "apihelp-imagerotate-description": "Ein oder mehrere Bilder drehen.",
+ "apihelp-imagerotate-param-rotation": "Anzahl der Grad, um die das Bild im Uhrzeigersinn gedreht werden soll.",
+ "apihelp-imagerotate-example-simple": "<kbd>Datei:Beispiel.png</kbd> um <kbd>90</kbd> Grad drehen.",
+ "apihelp-imagerotate-example-generator": "Alle Bilder in der <kbd>Kategorie:Flip</kbd> um <kbd>180</kbd> Grad drehen.",
+ "apihelp-import-description": "Importiert eine Seite von einem anderen Wiki oder einer XML-Datei.\n\nBitte beachte, dass der HTTP-POST-Vorgang als Dateiupload ausgeführt werden muss (z.B. durch multipart/form-data), um eine Datei über den <var>xml</var>-Parameter zu senden.",
+ "apihelp-import-param-summary": "Import-Zusammenfassung.",
+ "apihelp-import-param-xml": "Hochgeladene XML-Datei.",
+ "apihelp-import-param-interwikisource": "Für Interwiki-Importe: Wiki, von dem importiert werden soll.",
+ "apihelp-import-param-interwikipage": "Für Interwiki-Importe: zu importierende Seite.",
+ "apihelp-import-param-fullhistory": "Für Interwiki-Importe: importiere die komplette Versionsgeschichte, nicht nur die aktuelle Version.",
+ "apihelp-import-param-templates": "Für Interwiki-Importe: importiere auch alle eingebundenen Vorlagen.",
+ "apihelp-import-param-namespace": "Für Interwiki-Importe: importiere in diesen Namensraum.",
+ "apihelp-import-param-rootpage": "Als Unterseite dieser Seite importieren.",
+ "apihelp-import-example-import": "Importiere [[meta:Help:Parserfunctions]] mit der kompletten Versionsgeschichte in den Namensraum 100.",
+ "apihelp-login-description": "Anmelden und Authentifizierungs-Cookies beziehen.\n\nFalls das Anmelden erfolgreich war, werden die benötigten Cookies im Header der HTTP-Antwort des Servers übermittelt. Bei fehlgeschlagenen Anmeldeversuchen können weitere Versuche gedrosselt werden, um automatische Passwortermittlungsattacken zu verhinden.",
+ "apihelp-login-param-name": "Benutzername.",
+ "apihelp-login-param-password": "Passwort.",
+ "apihelp-login-param-domain": "Domain (optional).",
+ "apihelp-login-param-token": "Anmeldetoken, den du in der ersten Anfrage erhalten hast.",
+ "apihelp-login-example-gettoken": "Ruft einen Anmelde-Token ab",
+ "apihelp-login-example-login": "Anmelden",
+ "apihelp-logout-description": "Abmelden und alle Sitzungsdaten löschen.",
+ "apihelp-logout-example-logout": "Meldet den aktuellen Benutzer ab",
+ "apihelp-managetags-description": "Ermöglicht Verwaltungsaufgaben zu Änderungsmarkierungen.",
+ "apihelp-managetags-param-reason": "optionale Begründung für das Erstellen, Löschen, Aktivieren oder Deaktivieren der Markierung.",
+ "apihelp-managetags-param-ignorewarnings": "Warnungen während des Vorgangs ignorieren.",
+ "apihelp-managetags-example-create": "Erstellt eine Markierung namens <kbd>spam</kbd> mit der Begründung <kbd>For use in edit patrolling</kbd> (für die Eingangskontrolle).",
+ "apihelp-managetags-example-delete": "Löscht die <kbd>vandlaism</kbd>-Markierung mit der Begründung <kbd>Misspelt</kbd>.",
+ "apihelp-managetags-example-activate": "Aktiviert eine Markierung namens <kbd>spam</kbd> mit der Begründung <kbd>For use in edit patrolling</kbd> (für die Eingangskontrolle).",
+ "apihelp-managetags-example-deactivate": "Deaktiviert eine Markierung namens <kbd>spam</kbd> mit der Begründung <kbd>No longer required</kbd> (nicht mehr benötigt).",
+ "apihelp-move-description": "Eine Seite verschieben.",
+ "apihelp-move-param-from": "Titel der zu verschiebenden Seite. Kann nicht zusammen mit <var>$1fromid</var> verwendet werden.",
+ "apihelp-move-param-fromid": "Seitenkennung der zu verschiebenden Seite. Kann nicht zusammen mit <var>$1from</var> verwendet werden.",
+ "apihelp-move-param-to": "Titel, zu dem die Seite umbenannt werden soll.",
+ "apihelp-move-param-reason": "Grund für die Umbenennung.",
+ "apihelp-move-param-movetalk": "Verschiebt die Diskussionsseite, falls vorhanden.",
+ "apihelp-move-param-movesubpages": "Unterseiten verschieben, falls möglich.",
+ "apihelp-move-param-noredirect": "Keine Weiterleitung erstellen.",
+ "apihelp-move-param-watch": "Die Seite und die entstandene Weiterleitung zur Beobachtungsliste hinzufügen.",
+ "apihelp-move-param-unwatch": "Die Seite und die entstandene Weiterleitung von der Beobachtungsliste entfernen.",
+ "apihelp-move-param-watchlist": "Die Seite in jedem Fall zur Beobachtungsliste hinzufügen oder davon entfernen, die Voreinstellungen dafür nutzen oder den Beobachtungsstatus nicht ändern.",
+ "apihelp-move-param-ignorewarnings": "Alle Warnungen ignorieren.",
+ "apihelp-move-example-move": "<kbd>Schlechter Titel</kbd> nach <kbd>Guter Titel</kbd> verschieben, ohne eine Weiterleitung zu erstellen.",
+ "apihelp-opensearch-description": "Das Wiki mithilfe des OpenSearch-Protokolls durchsuchen.",
+ "apihelp-opensearch-param-search": "Such-Zeichenfolge.",
+ "apihelp-opensearch-param-limit": "Maximale Anzahl zurückzugebender Ergebnisse.",
+ "apihelp-opensearch-param-namespace": "Zu durchsuchende Namensräume.",
+ "apihelp-opensearch-param-suggest": "Nichts unternehmen, falls <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> falsch ist.",
+ "apihelp-opensearch-param-format": "Das Format der Ausgabe.",
+ "apihelp-opensearch-example-te": "Seiten finden, die mit <kbd>Te</kbd> beginnen.",
+ "apihelp-options-param-reset": "Setzt die Einstellungen auf Websitestandards zurück.",
+ "apihelp-options-example-reset": "Alle Einstellungen zurücksetzen",
+ "apihelp-options-example-change": "Ändert die Einstellungen <kbd>skin</kbd> und <kbd>hideminor</kbd>.",
+ "apihelp-options-example-complex": "Setzt alle Einstellungen zurück, dann <kbd>skin</kbd> und <kbd>nickname</kbd> festlegen.",
+ "apihelp-paraminfo-description": "Ruft Informationen über API-Module ab.",
+ "apihelp-paraminfo-param-helpformat": "Format der Hilfe-Zeichenfolgen.",
+ "apihelp-parse-param-summary": "Zu parsende Zusammenfassung.",
+ "apihelp-parse-param-section": "Gibt nur den Inhalt dieses Abschnittes zurück oder erstellt einen neuen Abschnitt, wenn <kbd>new</kbd> angegeben wird.\n\n<kbd>new</kbd> wird nur ausgewertet, wenn auch <var>text</var> angegeben wurde.",
+ "apihelp-parse-param-sectiontitle": "Überschrift des neuen Abschnittes, wenn <var>section</var> = <kbd>new</kbd> ist.\n\nAnders als beim Bearbeiten der Seite wird der Parameter nicht durch die <var>summary</var> ersetzt, wenn er weggelassen oder leer ist.",
+ "apihelp-parse-param-disableeditsection": "Deaktiviert Abschnittsbearbeitungslinks in der Parserausgabe.",
+ "apihelp-parse-param-preview": "Im Vorschaumodus parsen.",
+ "apihelp-parse-param-disabletoc": "Inhaltsverzeichnis in der Ausgabe deaktivieren.",
+ "apihelp-parse-example-page": "Eine Seite parsen.",
+ "apihelp-parse-example-text": "Wikitext parsen.",
+ "apihelp-parse-example-texttitle": "Parst den Wikitext über die Eingabe des Seitentitels.",
+ "apihelp-parse-example-summary": "Parst eine Zusammenfassung.",
+ "apihelp-patrol-description": "Kontrolliert eine Seite oder Version.",
+ "apihelp-patrol-param-rcid": "Letzte-Änderungen-Kennung, die kontrolliert werden soll.",
+ "apihelp-patrol-param-revid": "Versionskennung, die kontrolliert werden soll.",
+ "apihelp-patrol-example-rcid": "Kontrolliert eine kürzlich getätigte Änderung.",
+ "apihelp-patrol-example-revid": "Kontrolliert eine Version",
+ "apihelp-protect-description": "Ändert den Schutzstatus einer Seite.",
+ "apihelp-protect-param-title": "Titel der Seite, die du (ent-)sperren möchtest. Kann nicht zusammen mit $1pageid verwendet werden.",
+ "apihelp-protect-param-pageid": "Seitenkennung der Seite, die du (ent-)sperren möchtest. Kann nicht zusammen mit $1title verwendet werden.",
+ "apihelp-protect-param-protections": "Liste der Schutzebenen nach dem Format <kbd>Aktion=Ebene</kbd> (z.B. <kbd>edit=sysop</kbd>).\n\n<strong>HINWEIS:</strong> Wenn eine Aktion nicht angegeben wird, wird deren Schutz entfernt.",
+ "apihelp-protect-param-expiry": "Zeitstempel des Schutzablaufs. Wenn nur ein Zeitstempel übergeben wird, ist dieser für alle Seitenschutze gültig. Um eine unendliche Schutzdauer festzulegen, kannst du die Werte <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd> oder <kbd>never</kbd> übergeben.",
+ "apihelp-protect-param-reason": "Grund für den Seitenschutz oder dessen Aufhebung.",
+ "apihelp-protect-param-cascade": "Aktiviert den Kaskadenschutz (alle eingebundenen Seiten werden ebenfalls geschützt). Wenn die übergebenen Schutzebenen keinen Kaskadenschutz unterstützen, wird dieser Parameter ignoriert.",
+ "apihelp-protect-param-watch": "Wenn vorhanden, fügt dieser Parameter die zu (ent-)sperrende Seite der Beobachtungsliste des aktuellen Benutzers hinzu.",
+ "apihelp-protect-param-watchlist": "Die Seite bedingungslos zur Beobachtungsliste des aktuellen Benutzers hinzufügen oder von ihr entfernen, Einstellungen verwenden oder Beobachtung nicht ändern.",
+ "apihelp-protect-example-protect": "Schützt eine Seite",
+ "apihelp-protect-example-unprotect": "Eine Seite entsperren, indem die Einschränkungen durch den Schutz auf <kbd>all</kbd> gestellt werden.",
+ "apihelp-protect-example-unprotect2": "Eine Seite entsperren, indem keine Einschränkungen übergeben werden",
+ "apihelp-purge-description": "Setzt den Cache der angegebenen Seiten zurück.\n\nFalls kein Benutzer angemeldet ist, müssen POST-Anfragen genutzt werden.",
+ "apihelp-purge-param-forcelinkupdate": "Aktualisiert die Linktabellen.",
+ "apihelp-purge-param-forcerecursivelinkupdate": "Aktualisiert die Linktabelle der Seite und alle Linktabellen der Seiten, die sie als Vorlage einbinden.",
+ "apihelp-purge-example-simple": "Purgt die <kbd>Main Page</kbd> und die <kbd>API</kbd>-Seite.",
+ "apihelp-purge-example-generator": "Purgt die ersten 10 Seiten des Hauptnamensraums.",
+ "apihelp-query-description": "Bezieht Daten von und über MediaWiki.\n\nAlle Änderungsvorgänge müssen unter Angabe eines Tokens ablaufen, um Missbrauch durch böswillige Anwendungen vorzubeugen.",
+ "apihelp-query-param-prop": "Zurückzuliefernde Eigenschaften der abgefragten Seiten.",
+ "apihelp-query-param-list": "Welche Listen abgerufen werden sollen.",
+ "apihelp-query-param-meta": "Zurückzugebende Metadaten.",
+ "apihelp-query-param-indexpageids": "Schließt einen zusätzlichen pageids-Abschnitt mit allen zurückgegebenen Seitenkennungen ein.",
+ "apihelp-query-param-export": "Exportiert die aktuellen Versionen der angegebenen oder generierten Seiten.",
+ "apihelp-query-param-exportnowrap": "Gibt den XML-Export zurück, ohne ihn in ein XML-Ergebnis einzuschließen (gleiches Format wie durch [[Special:Export]]). Kann nur zusammen mit $1export genutzt werden.",
+ "apihelp-query-param-iwurl": "Gibt an, ob die komplette URL zurückgegeben werden soll, wenn der Titel ein Interwikilink ist.",
+ "apihelp-query-param-continue": "Falls angegeben, wird query-continue als Schlüssel-Wert-Paar zurückgegeben, das anschließend an die Folgeabfrage gehängt werden kann. Dieser Parameter muss in der ersten Abfrage auf leer gesetzt werden.\n\nDieser Parameter ist für alle Neuentwicklungen empfohlen und wird in der nächsten API-Version Standard.",
+ "apihelp-query-param-rawcontinue": "Momentan unbeachtet. In Zukunft wird <var>$1continue</var> Standard, dieser Parameter wird dann benötigt, um die rohen <samp>query-continue</samp>-Daten zu erhalten.",
+ "apihelp-query-example-revisions": "Bezieht [[Special:ApiHelp/query+siteinfo|Seiteninformationen]] und [[Special:ApiHelp/query+revisions|Versionen]] der <kbd>Main Page</kbd>.",
+ "apihelp-query-example-allpages": "Bezieht Versionen von Seiten, die mit <kbd>API/</kbd> beginnen.",
+ "apihelp-query+allcategories-description": "Alle Kategorien aufzählen.",
+ "apihelp-query+allcategories-param-from": "Kategorie, bei der die Auflistung beginnen soll.",
+ "apihelp-query+allcategories-param-to": "Kategorie, bei der die Auflistung enden soll.",
+ "apihelp-query+allcategories-param-prefix": "Listet alle Kategorien auf, die mit dem angegebenen Wert beginnen.",
+ "apihelp-query+allcategories-param-dir": "Sortierrichtung.",
+ "apihelp-query+allcategories-param-min": "Gibt nur Kategorien zurück, die mindestens die angegebene Anzahl an Einträgen haben.",
+ "apihelp-query+allcategories-param-max": "Gibt nur Kategorien zurück, die höchstens die angegebene Anzahl an Einträgen haben.",
+ "apihelp-query+allcategories-param-limit": "Wie viele Kategorien zurückgegeben werden sollen.",
+ "apihelp-query+allcategories-param-prop": "Zurückzugebende Eigenschaften:\n;size: Ergänzt die Anzahl der Einträge in der Antwort.\n;hidden: Markiert über _&#95;HIDDENCAT_&#95; versteckte Kategorien.",
+ "apihelp-query+allcategories-example-size": "Listet Kategorien mit der Anzahl ihrer Einträge auf.",
+ "apihelp-query+allcategories-example-generator": "Bezieht Informationen über die Kategorieseite selbst für Kategorien, die mit <kbd>List</kbd> beginnen.",
+ "apihelp-query+alldeletedrevisions-description": "Bezieht alle gelöschten Versionen eines Benutzers oder eines Namensraumes.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Darf nur mit <var>$3user</var> verwendet werden.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Kann nicht zusammen mit <var>$3user</var> benutzt werden.",
+ "apihelp-query+alldeletedrevisions-param-start": "Der Zeitstempel, bei dem die Auflistung beginnen soll.",
+ "apihelp-query+alldeletedrevisions-param-end": "Der Zeitstempel, bei dem die Auflistung enden soll.",
+ "apihelp-query+alldeletedrevisions-param-from": "Seitentitel, bei dem die Auflistung beginnen soll.",
+ "apihelp-query+alldeletedrevisions-param-to": "Seitentitel, bei dem die Auflistung enden soll.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Listet alle Seitentitel auf, die mit dem angegebenen Wert beginnen.",
+ "apihelp-query+alldeletedrevisions-param-tag": "Listet nur Versionen auf, die die angegebene Markierung haben.",
+ "apihelp-query+alldeletedrevisions-param-user": "Nur Versionen von diesem Benutzer auflisten.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Schließt Bearbeitungen des angegebenen Benutzers aus.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Nur Seiten in diesem Namensraum auflisten.",
+ "apihelp-query+allfileusages-param-limit": "Wie viele Gesamtobjekte zurückgegeben werden sollen.",
+ "apihelp-query+allfileusages-example-unique": "Einheitliche Dateititel auflisten",
+ "apihelp-query+allfileusages-example-generator": "Seiten abrufen, die die Dateien enthalten",
+ "apihelp-query+allimages-description": "Alle Bilder nacheinander auflisten.",
+ "apihelp-query+allimages-param-sha1": "SHA1-Hash des Bildes. Überschreibt $1sha1base36.",
+ "apihelp-query+allimages-param-sha1base36": "SHA1-Hash des Bildes (Basis 36; verwendet in MediaWiki).",
+ "apihelp-query+allimages-param-limit": "Wie viele Gesamtbilder zurückgegeben werden sollen.",
+ "apihelp-query+allimages-example-recent": "Zeigt eine Liste von kürzlich hochgeladenen Dateien ähnlich zu [[Special:NewFiles]].",
+ "apihelp-query+alllinks-example-unique": "Einheitlich verlinkte Titel auflisten",
+ "apihelp-query+allpages-param-filterredir": "Welche Seiten aufgelistet werden sollen.",
+ "apihelp-query+allredirects-example-unique": "Einzigartige Zielseiten auflisten.",
+ "apihelp-query+allredirects-example-generator": "Seiten abrufen, die die Weiterleitungen enthalten",
+ "apihelp-query+alltransclusions-param-namespace": "Der aufzulistende Namensraum.",
+ "apihelp-query+alltransclusions-example-unique": "Einzigartige eingebundene Titel auflisten.",
+ "apihelp-query+allusers-param-limit": "Wie viele Benutzernamen insgesamt zurückgegeben werden sollen.",
+ "apihelp-query+allusers-example-Y": "Benutzer ab <kbd>Y</kbd> auflisten.",
+ "apihelp-query+backlinks-description": "Alle Seiten finden, die auf die angegebene Seite verlinken.",
+ "apihelp-query+backlinks-example-simple": "Links auf <kbd>Hauptseite</kbd> anzeigen.",
+ "apihelp-query+blocks-example-simple": "Sperren auflisten",
+ "apihelp-query+categorymembers-param-startsortkey": "Stattdessen $1starthexsortkey verwenden.",
+ "apihelp-query+categorymembers-param-endsortkey": "Stattdessen $1endhexsortkey verwenden.",
+ "apihelp-query+contributors-param-limit": "Wie viele Spender zurückgegeben werden sollen.",
+ "apihelp-query+deletedrevisions-param-user": "Nur Versionen von diesem Benutzer auflisten.",
+ "apihelp-query+deletedrevisions-param-limit": "Die Maximalmenge der aufzulistenden Versionen.",
+ "apihelp-query+deletedrevs-param-from": "Auflistung bei diesem Titel beginnen.",
+ "apihelp-query+deletedrevs-param-to": "Auflistung bei diesem Titel beenden.",
+ "apihelp-query+duplicatefiles-param-localonly": "Sucht nur nach Dateien im lokalen Repositorium.",
+ "apihelp-query+duplicatefiles-example-simple": "Sucht nach Duplikaten von [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+duplicatefiles-example-generated": "Sucht nach Duplikaten aller Dateien.",
+ "apihelp-query+embeddedin-param-namespace": "Der aufzulistende Namensraum.",
+ "apihelp-query+embeddedin-param-filterredir": "Wie Weiterleitungen behandelt werden sollen.",
+ "apihelp-query+embeddedin-param-limit": "Wie viele Seiten insgesamt zurückgegeben werden sollen.",
+ "apihelp-query+extlinks-param-limit": "Wie viele Links zurückgegeben werden sollen.",
+ "apihelp-query+exturlusage-param-limit": "Wie viele Seiten zurückgegeben werden sollen.",
+ "apihelp-query+filearchive-param-from": "Der Bildertitel, bei dem die Auflistung beginnen soll.",
+ "apihelp-query+filearchive-param-to": "Der Bildertitel, bei dem die Auflistung enden soll.",
+ "apihelp-query+filearchive-param-limit": "Wie viele Bilder insgesamt zurückgegeben werden sollen.",
+ "apihelp-query+filearchive-example-simple": "Eine Liste aller gelöschten Dateien auflisten",
+ "apihelp-query+imageinfo-param-limit": "Wie viele Dateiversionen pro Datei zurückgegeben werden sollen.",
+ "apihelp-query+imageinfo-param-start": "Zeitstempel, von dem die Liste beginnen soll.",
+ "apihelp-query+imageinfo-param-end": "Zeitstempel, an dem die Liste enden soll.",
+ "apihelp-query+imageinfo-param-urlheight": "Ähnlich wie $1urlwidth.",
+ "apihelp-query+info-description": "Ruft Basisinformationen über die Seite ab.",
+ "apihelp-query+info-param-testactions": "Überprüft, ob der aktuelle Benutzer gewisse Aktionen auf der Seite ausführen kann.",
+ "apihelp-query+iwbacklinks-param-prefix": "Präfix für das Interwiki.",
+ "apihelp-query+langbacklinks-param-limit": "Wie viele Gesamtseiten zurückgegeben werden sollen.",
+ "apihelp-query+links-example-simple": "Links von der <kbd>Hauptseite</kbd> abrufen",
+ "apihelp-query+linkshere-description": "Alle Seiten finden, die auf die angegebenen Seiten verlinken.",
+ "apihelp-query+logevents-description": "Ereignisse von den Logbüchern abrufen.",
+ "apihelp-query+prefixsearch-param-search": "Such-Zeichenfolge.",
+ "apihelp-query+search-example-simple": "Nach <kbd>meaning</kbd> suchen.",
+ "apihelp-query+search-example-text": "Texte nach <kbd>meaning</kbd> durchsuchen.",
+ "apihelp-query+siteinfo-example-simple": "Websiteinformationen abrufen",
+ "apihelp-query+tags-description": "Änderungs-Tags auflisten.",
+ "apihelp-query+tags-example-simple": "Verfügbare Tags auflisten",
+ "apihelp-query+usercontribs-description": "Alle Bearbeitungen von einem Benutzer abrufen.",
+ "apihelp-query+userinfo-example-simple": "Informationen über den aktuellen Benutzer abrufen",
+ "apihelp-query+users-description": "Informationen über eine Liste von Benutzern abrufen.",
+ "apihelp-rsd-description": "Ein RSD-Schema (Really Simple Discovery) exportieren.",
+ "apihelp-rsd-example-simple": "Das RSD-Schema exportieren",
+ "apihelp-setnotificationtimestamp-param-entirewatchlist": "An allen beobachteten Seiten arbeiten.",
+ "apihelp-unblock-description": "Einen Benutzer freigeben.",
+ "apihelp-unblock-param-reason": "Grund für die Freigabe.",
+ "apihelp-unblock-example-id": "Sperrkennung #<kbd>105</kbd> freigeben.",
+ "apihelp-undelete-param-reason": "Grund für die Wiederherstellung.",
+ "apihelp-upload-param-filename": "Ziel-Dateiname.",
+ "apihelp-upload-param-watch": "Die Seite beobachten.",
+ "apihelp-upload-param-file": "Dateiinhalte.",
+ "apihelp-upload-param-url": "URL, von der die Datei abgerufen werden soll.",
+ "apihelp-upload-example-url": "Von einer URL hochladen",
+ "apihelp-userrights-param-user": "Benutzername.",
+ "apihelp-userrights-param-userid": "Benutzerkennung.",
+ "apihelp-watch-example-watch": "Die Seite <kbd>Hauptseite</kbd> beobachten.",
+ "apihelp-format-example-generic": "Das Abfrageergebnis im $1-Format formatieren",
+ "apihelp-dbg-description": "Daten im PHP-<code>var_export()</code>-Format ausgeben.",
+ "apihelp-dbgfm-description": "Daten im PHP-<code>var_export()</code>-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-dump-description": "Daten im PHP-<code>var_dump()</code>-Format ausgeben.",
+ "apihelp-dumpfm-description": "Daten im PHP-<code>var_dump()</code>-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-json-description": "Daten im JSON-Format ausgeben.",
+ "apihelp-json-param-callback": "Falls angegeben, wird die Ausgabe in einen angegebenen Funktionsaufruf eingeschlossen. Aus Sicherheitsgründen sind benutzerspezifische Daten beschränkt.",
+ "apihelp-json-param-utf8": "Falls angegeben, kodiert die meisten (aber nicht alle) Nicht-ASCII-Zeichen als UTF-8 anstatt sie mit hexadezimalen Escape-Sequenzen zu ersetzen.",
+ "apihelp-jsonfm-description": "Daten im JSON-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-none-description": "Nichts ausgeben.",
+ "apihelp-php-description": "Daten im serialisierten PHP-Format ausgeben.",
+ "apihelp-phpfm-description": "Daten im serialisierten PHP-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-rawfm-description": "Daten mit den Fehlerbehebungselementen im JSON-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-txt-description": "Daten im PHP-<code>print_r()</code>-Format ausgeben.",
+ "apihelp-txtfm-description": "Daten im PHP-<code>print_r()</code>-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-wddx-description": "Daten im WDDX-Format ausgeben.",
+ "apihelp-wddxfm-description": "Daten im WDDX-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-xml-description": "Daten im XML-Format ausgeben.",
+ "apihelp-xml-param-xslt": "Falls angegeben, fügt die benannte Seite als XSL-Stylesheet hinzu. Der Wert muss ein Titel im Namensraum „{{ns:mediawiki}}“ sein und mit <code>.xsl</code> enden.",
+ "apihelp-xml-param-includexmlnamespace": "Falls angegeben, ergänzt einen XML-Namensraum.",
+ "apihelp-xmlfm-description": "Daten im XML-Format ausgeben (schöngedruckt in HTML).",
+ "apihelp-yaml-description": "Daten im YAML-Format ausgeben.",
+ "apihelp-yamlfm-description": "Daten im YAML-Format ausgeben (schöngedruckt in HTML).",
+ "api-format-title": "MediaWiki-API-Ergebnis",
+ "api-format-prettyprint-header": "Dies ist die HTML-Repräsentation des $1-Formats. HTML ist zur Fehlerbehebung gut, aber unpassend für den Anwendungsgebrauch.\n\nGib den Parameter <var>Format</var> an, um das Ausgabeformat zu ändern. Um die Nicht-HTML-Repräsentation des $1-Formats anzusehen, lege <kbd>format=$2</kbd> fest.\n\nSiehe die [[mw:API|vollständige Dokumentation]] oder die [[Special:ApiHelp/main|API-Hilfe]] für weitere Informationen.",
+ "api-orm-param-props": "Felder an die Anfrage.",
+ "api-orm-param-limit": "Maximale Anzahl zurückgegebender Zeilen.",
+ "api-pageset-param-titles": "Eine Liste der Titel, an denen gearbeitet werden soll.",
+ "api-pageset-param-pageids": "Eine Liste der Seitenkennungen, an denen gearbeitet werden soll.",
+ "api-pageset-param-revids": "Eine Liste der Versionskennungen, an denen gearbeitet werden soll.",
+ "api-help-title": "MediaWiki-API-Hilfe",
+ "api-help-lead": "Dies ist eine automatisch generierte MediaWiki-API-Dokumentationsseite.\n\nDokumentation und Beispiele: https://www.mediawiki.org/wiki/API/de",
+ "api-help-main-header": "Hauptmodul",
+ "api-help-flag-deprecated": "Dieses Modul ist veraltet.",
+ "api-help-flag-internal": "<strong>Dieses Modul ist intern oder instabil.</strong> Seine Operationen werden ohne Kenntnisnahme geändert.",
+ "api-help-flag-readrights": "Dieses Modul erfordert Leserechte.",
+ "api-help-flag-writerights": "Dieses Modul erfordert Schreibrechte.",
+ "api-help-flag-mustbeposted": "Dieses Modul akzeptiert nur POST-Anfragen.",
+ "api-help-flag-generator": "Dieses Modul kann als Generator verwendet werden.",
+ "api-help-parameters": "{{PLURAL:$1|Parameter}}:",
+ "api-help-param-deprecated": "Veraltet.",
+ "api-help-param-required": "Dieser Parameter ist erforderlich.",
+ "api-help-param-list": "{{PLURAL:$1|1=Ein Wert|2=Werte (mit <kbd>{{!}}</kbd> trennen)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Muss leer sein|Kann leer sein oder $2}}",
+ "api-help-param-limit": "Nicht mehr als $1 erlaubt.",
+ "api-help-param-limit2": "Nicht mehr als $1 ($2 für Bots) erlaubt.",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=Der Wert darf|2=Die Werte dürfen}} nicht kleiner sein als $2.",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=Der Wert darf|2=Die Werte dürfen}} nicht größer sein als $3.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=Der Wert muss|2=Die Werte müssen}} zwischen $2 und $3 sein.",
+ "api-help-param-upload": "Muss als Dateiupload mithilfe eines multipart/form-data-Formular bereitgestellt werden.",
+ "api-help-param-multi-separate": "Werte mit <kbd>|</kbd> trennen.",
+ "api-help-param-multi-max": "Maximale Anzahl der Werte ist {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} für Bots).",
+ "api-help-param-default": "Standard: $1",
+ "api-help-param-default-empty": "Standard: <span class=\"apihelp-empty\">(leer)</span>",
+ "api-help-param-token": "Ein „$1“-Token abgerufen von [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(keine Beschreibung)</span>",
+ "api-help-examples": "{{PLURAL:$1|Beispiel|Beispiele}}:",
+ "api-help-permissions": "{{PLURAL:$1|Berechtigung|Berechtigungen}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Gewährt an}}: $2",
+ "api-help-right-apihighlimits": "Höhere Beschränkungen in API-Anfragen verwenden (langsame Anfragen: $1; schnelle Anfragen: $2). Die Beschränkungen für langsame Anfragen werden auch auf Mehrwertparameter angewandt.",
+ "api-credits-header": "Danksagungen",
+ "api-credits": "API-Entwickler:\n* Roan Kattouw (Hauptentwickler von September 2007 bis 2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (Autor, Hauptentwickler von September 2006 bis September 2007)\n* Brad Jorsch (Hauptentwickler seit 2013)\n\nBitte sende deine Kommentare, Vorschläge und Fragen an mediawiki-api@lists.wikimedia.org\noder reiche einen Fehlerbericht auf https://phabricator.wikimedia.org/ ein."
+}
diff --git a/includes/api/i18n/el.json b/includes/api/i18n/el.json
new file mode 100644
index 00000000..d4d239f4
--- /dev/null
+++ b/includes/api/i18n/el.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Glavkos"
+ ]
+ },
+ "apihelp-block-description": "Φραγή χρήστη",
+ "apihelp-block-param-user": "Όνομα χρήστη, διεύθυνση IP ή εύρος διευθύνσεων IP που θέλετε να επιβάλετε φραγή.",
+ "apihelp-block-param-reason": "Λόγος φραγής.",
+ "apihelp-createaccount-param-name": "Όνομα χρήστη.",
+ "apihelp-delete-description": "Διαγραφή σελίδας."
+}
diff --git a/includes/api/i18n/en-gb.json b/includes/api/i18n/en-gb.json
new file mode 100644
index 00000000..929ec7e0
--- /dev/null
+++ b/includes/api/i18n/en-gb.json
@@ -0,0 +1,156 @@
+{
+ "@metadata": {
+ "authors": [
+ "Reedy",
+ "Chase me ladies, I'm the Cavalry"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Mailing list]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API Announcements]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bugs & requests]\n</div>\n<strong>Status:</strong> All features shown on this page should be working, but the API is still in active development, and may change at any time. Subscribe to [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce mailing list] for notice of updates.\n\n<strong>Erroneous requests:</strong> When erroneous requests are sent to the API, an 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. For more information see [[mw:API:Errors_and_warnings|API: Errors and warnings]].",
+ "apihelp-main-param-maxage": "Set the <code>max-age</code> header to this many seconds. Errors are never cached.",
+ "apihelp-main-param-assert": "Verify the user is logged in if set to <kbd>user</kbd>, or has the bot userright if <kbd>bot</kbd>.",
+ "apihelp-block-param-user": "Username, IP address, or IP range to block.",
+ "apihelp-block-param-allowusertalk": "Allow the user to edit their own talk page (depends on <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-watchuser": "Watch the user and talk pages of the user or IP address.",
+ "apihelp-block-example-ip-simple": "Block IP address <kbd>192.0.2.5</kbd> for three days with reason <kbd>First strike</kbd>.",
+ "apihelp-clearhasmsg-description": "Clears the <code>hasmsg</code> flag for the current user.",
+ "apihelp-compare-description": "Get the difference between 2 pages.\n\nA revision number, a page title, or a page ID for both \"from\" and \"to\" must be passed.",
+ "apihelp-createaccount-param-password": "Password (ignored if <var>$1mailpassword</var> is set).",
+ "apihelp-delete-param-title": "Title of the page to delete. Cannot be used together with <var>$1pageid</var>.",
+ "apihelp-delete-param-watch": "Add the page to the current user's watchlist.",
+ "apihelp-delete-example-simple": "Delete <kbd>Main Page</kbd>.",
+ "apihelp-delete-example-reason": "Delete <kbd>Main Page</kbd> with the reason <kbd>Preparing for move</kbd>.",
+ "apihelp-edit-param-title": "Title of the page to edit. Cannot be used together with <var>$1pageid</var>.",
+ "apihelp-edit-param-watch": "Add the page to the current user's watchlist.",
+ "apihelp-edit-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-edit-param-contentformat": "Content serialisation format used for the input text.",
+ "apihelp-edit-example-prepend": "Prepend <kbd>_&#95;NOTOC_&#95;</kbd> to a page.",
+ "apihelp-expandtemplates-example-simple": "Expand the wikitext <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Hide changes made by the current user.",
+ "apihelp-filerevert-example-revert": "Revert <kbd>Wiki.png</kbd> to the version of <kbd>2011-03-05T15:27:40Z</kbd>.",
+ "apihelp-help-example-main": "Help for the main module.",
+ "apihelp-help-example-query": "Help for two query submodules.",
+ "apihelp-import-description": "Import a page from another wiki, or an XML file.\n\nNote that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when sending a file for the <var>xml</var> parameter.",
+ "apihelp-login-example-gettoken": "Retrieve a login token.",
+ "apihelp-logout-example-logout": "Log the current user out.",
+ "apihelp-move-param-to": "Title to rename the page to.",
+ "apihelp-move-param-reason": "Reason for the rename.",
+ "apihelp-move-example-move": "Move <kbd>Badtitle</kbd> to <kbd>Goodtitle</kbd> without leaving a redirect.",
+ "apihelp-opensearch-example-te": "Find pages beginning with <kbd>Te</kbd>.",
+ "apihelp-options-param-resetkinds": "List of types of options to reset when the <var>$1reset</var> option is set.",
+ "apihelp-options-param-optionvalue": "A value of the option specified by <var>$1optionname</var>, can contain pipe characters.",
+ "apihelp-options-example-change": "Change <kbd>skin</kbd> and <kbd>hideminor</kbd> preferences.",
+ "apihelp-options-example-complex": "Reset all preferences, then set <kbd>skin</kbd> and <kbd>nickname</kbd>.",
+ "apihelp-paraminfo-param-querymodules": "List of query module names (value of <var>prop</var>, <var>meta</var> or <var>list</var> parameter). Use <kbd>$1modules=query+foo</kbd> instead of <kbd>$1querymodules=foo</kbd>.",
+ "apihelp-parse-param-pageid": "Parse the content of this page. Overrides <var>$1page</var>.",
+ "apihelp-parse-param-contentformat": "Content serialisation format used for the input text. Only valid when used with $1text.",
+ "apihelp-patrol-example-rcid": "Patrol a recent change.",
+ "apihelp-patrol-example-revid": "Patrol a revision.",
+ "apihelp-protect-param-protections": "List of protection levels, formatted <kbd>action=level</kbd> (e.g. <kbd>edit=sysop</kbd>).\n\n<strong>Note:</strong> Any actions not listed will have restrictions removed.",
+ "apihelp-protect-param-expiry": "Expiry timestamps. If only one timestamp is set, it will be used for all protections. Use <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd>, or <kbd>never</kbd>, for a never-expiring protection.",
+ "apihelp-protect-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-protect-example-unprotect2": "Unprotect a page by setting no restrictions.",
+ "apihelp-purge-example-simple": "Purge the <kbd>Main Page</kbd> and the <kbd>API</kbd> page.",
+ "apihelp-query-example-allpages": "Fetch revisions of pages beginning with <kbd>API/</kbd>.",
+ "apihelp-query+allcategories-example-generator": "Retrieve information about the category page for categories beginning <kbd>List</kbd>.",
+ "apihelp-query+allfileusages-example-unique-generator": "Gets all file titles, marking the missing ones.",
+ "apihelp-query+alllinks-param-unique": "Only show distinct linked titles. Cannot be used with <kbd>$1prop=ids</kbd>.\nWhen used as a generator, yields target pages instead of source pages.",
+ "apihelp-query+alllinks-example-B": "List linked titles, including missing ones, with page IDs they are from, starting at <kbd>B</kbd>.",
+ "apihelp-query+alllinks-example-unique": "List unique linked titles.",
+ "apihelp-query+allmessages-example-ipb": "Show messages starting with <kbd>ipb-</kbd>.",
+ "apihelp-query+allmessages-example-de": "Show messages <kbd>august</kbd> and <kbd>mainpage</kbd> in German.",
+ "apihelp-query+allpages-example-B": "Show a list of pages starting at the letter <kbd>B</kbd>.",
+ "apihelp-query+allredirects-example-B": "List target pages, including missing ones, with page IDs they are from, starting at <kbd>B</kbd>.",
+ "apihelp-query+allredirects-example-generator": "Gets pages containing the redirects.",
+ "apihelp-query+alltransclusions-example-unique": "List unique transcluded titles.",
+ "apihelp-query+alltransclusions-example-generator": "Gets pages containing the transclusions.",
+ "apihelp-query+backlinks-param-pageid": "Page ID to search. Cannot be used together with <var>$1title</var>.",
+ "apihelp-query+backlinks-param-limit": "How many total pages to return. If <var>$1redirect</var> is enabled, limit applies to each level separately (which means up to 2 * <var>$1limit</var> results may be returned).",
+ "apihelp-query+backlinks-example-generator": "Get information about pages linking to <kbd>Main page<kbd>.",
+ "apihelp-query+blocks-param-ip": "Get all blocks applying to this IP or CIDR range, including range blocks.\nThis cannot be used together with <var>$3users</var>. CIDR ranges broader than IPv4/$1 or IPv6/$2 will not be not accepted.",
+ "apihelp-query+blocks-example-simple": "List blocks.",
+ "apihelp-query+blocks-example-users": "List blocks of users <kbd>Alice</kbd> and <kbd>Bob</kbd>.",
+ "apihelp-query+categoryinfo-example-simple": "Get information about <kbd>Category:Foo</kbd> and <kbd>Category:Bar</kbd>.",
+ "apihelp-query+categorymembers-param-pageid": "Page ID of the category to enumerate. Cannot be used together with <var>$1title</var>.",
+ "apihelp-query+categorymembers-param-endhexsortkey": "Sortkey to end listing from, as returned by <kbd>$1prop=sortkey</kbd>. Can only be used with <kbd>$1sort=sortkey</kbd>.",
+ "apihelp-query+deletedrevs-example-mode2": "List the last 50 deleted contributions by <kbd>Bob</kbd> (mode 2).",
+ "apihelp-query+deletedrevs-example-mode3-main": "List the first 50 deleted revisions in the main namespace (mode 3).",
+ "apihelp-query+deletedrevs-example-mode3-talk": "List the first 50 deleted pages in the {{ns:talk}} namespace (mode 3).",
+ "apihelp-query+duplicatefiles-example-simple": "Look for duplicates of [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+duplicatefiles-example-generated": "Look for duplicates of all files.",
+ "apihelp-query+extlinks-example-simple": "Get a list of external links on <kbd>Main Page<kbd>.",
+ "apihelp-query+exturlusage-param-protocol": "Protocol of the URL. If empty and <var>$1query</var> set, the protocol is <kbd>http</kbd>. Leave both this and <var>$1query</var> empty to list all external links.",
+ "apihelp-query+filerepoinfo-example-simple": "Get information about file repositories.",
+ "apihelp-query+imageinfo-param-metadataversion": "Version of metadata to use. If <kbd>latest</kbd> is specified, use latest version. Defaults to <kbd>1</kbd> for backwards compatibility.",
+ "apihelp-query+imageinfo-example-simple": "Fetch information about the current version of [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+images-example-simple": "Get a list of files used in the [[Main Page]].",
+ "apihelp-query+imageusage-example-simple": "Show pages using [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+imageusage-example-generator": "Get information about pages using [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+info-example-protection": "Get general and protection information about the page <kbd>Main Page</kbd>.",
+ "apihelp-query+iwbacklinks-example-simple": "Get pages linking to [[wikibooks:Test]].",
+ "apihelp-query+iwlinks-param-title": "Interwiki link to search for. Must be used with <var>$1prefix</var>.",
+ "apihelp-query+langbacklinks-example-simple": "Get pages linking to [[:fr:Test]].",
+ "apihelp-query+langbacklinks-example-generator": "Get information about pages linking to [[:fr:Test]].",
+ "apihelp-query+langlinks-param-title": "Link to search for. Must be used with <var>$1lang</var>.",
+ "apihelp-query+links-example-simple": "Get links from the page <kbd>Main Page</kbd>",
+ "apihelp-query+links-example-namespaces": "Get links from the page <kbd>Main Page</kbd> in the {{ns:user}} and {{ns:template}} namespaces.",
+ "apihelp-query+linkshere-example-simple": "Get a list of pages linking to the [[Main Page]].",
+ "apihelp-query+linkshere-example-generator": "Get information about pages linking to the [[Main Page]].",
+ "apihelp-query+logevents-example-simple": "List recent log events.",
+ "apihelp-query+pagepropnames-description": "List all page property names in use on the wiki.",
+ "apihelp-query+pageswithprop-description": "List all pages using a given page property.",
+ "apihelp-query+pageswithprop-example-generator": "Get page information about the first 10 pages using <code>_&#95;NOTOC_&#95;</code>.",
+ "apihelp-query+protectedtitles-example-generator": "Find links to protected titles in the main namespace.",
+ "apihelp-query+random-example-simple": "Return two random pages from the main namespace.",
+ "apihelp-query+random-example-generator": "Return page info about two random pages from the main namespace.",
+ "apihelp-query+recentchanges-example-simple": "List recent changes.",
+ "apihelp-query+redirects-example-generator": "Get information about all redirects to the [[Main Page]].",
+ "apihelp-query+revisions-example-first5-not-localhost": "Get first 5 revisions of the <kbd>Main Page</kbd> that were not made by anonymous user <kbd>127.0.0.1</kbd>.",
+ "apihelp-query+revisions+base-param-difftotext": "Text to diff each revision to. Only diffs a limited number of revisions. Overrides <var>$1diffto</var>. If <var>$1section</var> is set, only that section will be diffed against this text",
+ "apihelp-query+revisions+base-param-contentformat": "Serialisation format used for <var>$1difftotext</var> and expected for output of content.",
+ "apihelp-query+search-example-text": "Search texts for <kbd>meaning</kbd>.",
+ "apihelp-query+search-example-generator": "Get page information about the pages returned for a search for <kbd>meaning</kbd>.",
+ "apihelp-query+siteinfo-example-simple": "Fetch site information.",
+ "apihelp-query+siteinfo-example-replag": "Check the current replication lag.",
+ "apihelp-query+stashimageinfo-example-simple": "Returns information for a stashed file.",
+ "apihelp-query+tags-example-simple": "List available tags.",
+ "apihelp-query+templates-example-simple": "Get the templates used on the page <kbd>Main Page</kbd>.",
+ "apihelp-query+templates-example-generator": "Get information about the template pages used on <kbd>Main Page</kbd>.",
+ "apihelp-query+tokens-example-simple": "Retrieve a csrf token (the default).",
+ "apihelp-query+tokens-example-types": "Retrieve a watch token and a patrol token.",
+ "apihelp-query+transcludedin-example-simple": "Get a list of pages transcluding <kbd>Main Page</kbd>.",
+ "apihelp-query+transcludedin-example-generator": "Get information about pages transcluding <kbd>Main Page</kbd>.",
+ "apihelp-query+usercontribs-example-user": "Show contributions of user <kbd>Example</kbd>.",
+ "apihelp-query+userinfo-example-simple": "Get information about the current user.",
+ "apihelp-query+watchlist-example-simple": "List the top revision for recently changed pages on the watchlist of the current user.",
+ "apihelp-query+watchlist-example-generator": "Fetch page info for recently changed pages on the current user's watchlist.",
+ "apihelp-query+watchlistraw-description": "Get all pages on the current user's watchlist.",
+ "apihelp-query+watchlistraw-example-simple": "List pages on the watchlist of the current user.",
+ "apihelp-query+watchlistraw-example-generator": "Fetch page info for pages on the current user's watchlist.",
+ "apihelp-revisiondelete-example-revision": "Hide content for revision <kbd>12345</kbd> on the page <kbd>Main Page</kbd>.",
+ "apihelp-rollback-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-rollback-example-simple": "Roll back the last edits to page <kbd>Main Page</kbd> by user <kbd>Example</kbd>.",
+ "apihelp-setnotificationtimestamp-example-allpages": "Reset the notification status for pages in the <kbd>{{ns:user}}</kbd> namespace.",
+ "apihelp-unblock-param-id": "ID of the block to unblock (obtained through <kbd>list=blocks</kbd>). Cannot be used together with <var>$1user</var>.",
+ "apihelp-undelete-param-timestamps": "Timestamps of the revisions to restore. If both <var>$1timestamps</var> and <var>$1fileids</var> are empty, all will be restored.",
+ "apihelp-undelete-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-undelete-example-page": "Undelete page <kbd>Main Page</kbd>.",
+ "apihelp-undelete-example-revisions": "Undelete two revisions of page <kbd>Main Page</kbd>.",
+ "apihelp-upload-param-comment": "Upload comment. Also used as the initial page text for new files if <var>$1text</var> is not specified.",
+ "apihelp-upload-example-url": "Upload from a URL.",
+ "apihelp-upload-example-filekey": "Complete an upload that failed due to warnings.",
+ "apihelp-userrights-example-userid": "Add the user with ID <kbd>123</kbd> to group <kbd>bot</kbd>, and remove from groups <kbd>sysop</kbd> and <kbd>bureaucrat</kbd>.",
+ "apihelp-watch-param-title": "The page to (un)watch. Use <var>$1titles</var> instead.",
+ "apihelp-watch-example-unwatch": "Unwatch the page <kbd>Main Page</kbd>.",
+ "apihelp-dbg-description": "Output data in PHP's <code>var_export()</code> format.",
+ "apihelp-dbgfm-description": "Output data in PHP's <code>var_export()</code> format (pretty-print in HTML).",
+ "apihelp-dump-description": "Output data in PHP's <code>var_dump()</code> format.",
+ "apihelp-php-description": "Output data in serialised PHP format.",
+ "apihelp-phpfm-description": "Output data in serialised PHP format (pretty-print in HTML).",
+ "apihelp-txt-description": "Output data in PHP's <code>print_r()</code> format.",
+ "apihelp-txtfm-description": "Output data in PHP's <code>print_r()</code> format (pretty-print in HTML).",
+ "api-pageset-param-redirects-generator": "Automatically resolve redirects in <var>$1titles</var>, <var>$1pageids</var>, and <var>$1revids</var>, and in pages returned by <var>$1generator</var>.",
+ "api-pageset-param-redirects-nogenerator": "Automatically resolve redirects in <var>$1titles</var>, <var>$1pageids</var>, and <var>$1revids</var>.",
+ "api-help-param-multi-separate": "Separate values with <kbd>|</kbd>.",
+ "api-help-param-disabled-in-miser-mode": "Disabled due to [[mw:Manual:$wgMiserMode|miser mode]].",
+ "api-help-param-limited-in-miser-mode": "<strong>Note:</strong> Due to [[mw:Manual:$wgMiserMode|miser mode]], using this may result in fewer than <var>$1limit</var> results returned before continuing; in extreme cases, zero results may be returned."
+}
diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json
new file mode 100644
index 00000000..36a4d812
--- /dev/null
+++ b/includes/api/i18n/en.json
@@ -0,0 +1,1169 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anomie",
+ "Siebrand"
+ ]
+ },
+
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Mailing list]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API Announcements]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bugs & requests]\n</div>\n<strong>Status:</strong> All features shown on this page should be working, but the API is still in active development, and may change at any time. Subscribe to [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce mailing list] for notice of updates.\n\n<strong>Erroneous requests:</strong> When erroneous requests are sent to the API, an 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. For more information see [[mw:API:Errors_and_warnings|API: Errors and warnings]].",
+ "apihelp-main-param-action": "Which action to perform.",
+ "apihelp-main-param-format": "The format of the output.",
+ "apihelp-main-param-maxlag": "Maximum lag can be used when MediaWiki is installed on a database replicated cluster. To save actions causing any more site replication lag, this parameter can make the client wait until the replication lag is less than the specified value. In case of excessive lag, error code <samp>maxlag</samp> is returned with a message like <samp>Waiting for $host: $lag seconds lagged</samp>.<br />See [[mw:Manual:Maxlag_parameter|Manual: Maxlag parameter]] for more information.",
+ "apihelp-main-param-smaxage": "Set the <code>s-maxage</code> header to this many seconds. Errors are never cached.",
+ "apihelp-main-param-maxage": "Set the <code>max-age</code> header to this many seconds. Errors are never cached.",
+ "apihelp-main-param-assert": "Verify the user is logged in if set to <kbd>user</kbd>, or has the bot userright if <kbd>bot</kbd>.",
+ "apihelp-main-param-requestid": "Any value given here will be included in the response. May be used to distinguish requests.",
+ "apihelp-main-param-servedby": "Include the hostname that served the request in the results.",
+ "apihelp-main-param-curtimestamp": "Include the current timestamp in the result.",
+ "apihelp-main-param-origin": "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 <code>Origin</code> header exactly, so it has to be set to something like <kbd>https://en.wikipedia.org</kbd> or <kbd>https://meta.wikimedia.org</kbd>. If this parameter does not match the <code>Origin</code> header, a 403 response will be returned. If this parameter matches the <code>Origin</code> header and the origin is whitelisted, an <code>Access-Control-Allow-Origin</code> header will be set.",
+ "apihelp-main-param-uselang": "Language to use for message translations. A list of codes may be fetched from <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> with <kbd>siprop=languages</kbd>, or specify <kbd>user</kbd> to use the current user's language preference, or specify <kbd>content</kbd> to use this wiki's content language.",
+
+ "apihelp-block-description": "Block a user.",
+ "apihelp-block-param-user": "Username, IP address, or IP range to block.",
+ "apihelp-block-param-expiry": "Expiry time. May be relative (e.g. <kbd>5 months</kbd> or <kbd>2 weeks</kbd>) or absolute (e.g. <kbd>2014-09-18T12:34:56Z</kbd>). If set to <kbd>infinite</kbd>, <kbd>indefinite</kbd>, or <kbd>never</kbd>, the block will never expire.",
+ "apihelp-block-param-reason": "Reason for block.",
+ "apihelp-block-param-anononly": "Block anonymous users only (i.e. disable anonymous edits for this IP address).",
+ "apihelp-block-param-nocreate": "Prevent account creation.",
+ "apihelp-block-param-autoblock": "Automatically block the last used IP address, and any subsequent IP addresses they try to login from.",
+ "apihelp-block-param-noemail": "Prevent user from sending email through the wiki. (Requires the <code>blockemail</code> right).",
+ "apihelp-block-param-hidename": "Hide the username from the block log. (Requires the <code>hideuser</code> right).",
+ "apihelp-block-param-allowusertalk": "Allow the user to edit their own talk page (depends on <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "If the user is already blocked, overwrite the existing block.",
+ "apihelp-block-param-watchuser": "Watch the user's or IP address's user and talk pages.",
+ "apihelp-block-example-ip-simple": "Block IP address <kbd>192.0.2.5</kbd> for three days with reason <kbd>First strike</kbd>.",
+ "apihelp-block-example-user-complex": "Block user <kbd>Vandal</kbd> indefinitely with reason <kbd>Vandalism</kbd>, and prevent new account creation and email sending.",
+
+ "apihelp-checktoken-description": "Check the validity of a token from <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
+ "apihelp-checktoken-param-type": "Type of token being tested.",
+ "apihelp-checktoken-param-token": "Token to test.",
+ "apihelp-checktoken-param-maxtokenage": "Maximum allowed age of the token, in seconds.",
+ "apihelp-checktoken-example-simple": "Test the validity of a <kbd>csrf</kbd> token.",
+
+ "apihelp-clearhasmsg-description": "Clears the <code>hasmsg</code> flag for the current user.",
+ "apihelp-clearhasmsg-example-1": "Clear the <code>hasmsg</code> flag for the current user.",
+
+ "apihelp-compare-description": "Get the difference between 2 pages.\n\nA revision number, a page title, or a page ID for both \"from\" and \"to\" must be passed.",
+ "apihelp-compare-param-fromtitle": "First title to compare.",
+ "apihelp-compare-param-fromid": "First page ID to compare.",
+ "apihelp-compare-param-fromrev": "First revision to compare.",
+ "apihelp-compare-param-totitle": "Second title to compare.",
+ "apihelp-compare-param-toid": "Second page ID to compare.",
+ "apihelp-compare-param-torev": "Second revision to compare.",
+ "apihelp-compare-example-1": "Create a diff between revision 1 and 2.",
+
+ "apihelp-createaccount-description": "Create a new user account.",
+ "apihelp-createaccount-param-name": "Username.",
+ "apihelp-createaccount-param-password": "Password (ignored if <var>$1mailpassword</var> is set).",
+ "apihelp-createaccount-param-domain": "Domain for external authentication (optional).",
+ "apihelp-createaccount-param-token": "Account creation token obtained in first request.",
+ "apihelp-createaccount-param-email": "Email address of user (optional).",
+ "apihelp-createaccount-param-realname": "Real name of user (optional).",
+ "apihelp-createaccount-param-mailpassword": "If set to any value, a random password will be emailed to the user.",
+ "apihelp-createaccount-param-reason": "Optional reason for creating the account to be put in the logs.",
+ "apihelp-createaccount-param-language": "Language code to set as default for the user (optional, defaults to content language).",
+ "apihelp-createaccount-example-pass": "Create user <kbd>testuser</kbd> with password <kbd>test123</kbd>.",
+ "apihelp-createaccount-example-mail": "Create user <kbd>testmailuser</kbd> and email a randomly-generated password.",
+
+ "apihelp-delete-description": "Delete a page.",
+ "apihelp-delete-param-title": "Title of the page to delete. Cannot be used together with <var>$1pageid</var>.",
+ "apihelp-delete-param-pageid": "Page ID of the page to delete. Cannot be used together with <var>$1title</var>.",
+ "apihelp-delete-param-reason": "Reason for the deletion. If not set, an automatically generated reason will be used.",
+ "apihelp-delete-param-watch": "Add the page to the current user's watchlist.",
+ "apihelp-delete-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-delete-param-unwatch": "Remove the page from the current user's watchlist.",
+ "apihelp-delete-param-oldimage": "The name of the old image to delete as provided by [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]].",
+ "apihelp-delete-example-simple": "Delete <kbd>Main Page</kbd>.",
+ "apihelp-delete-example-reason": "Delete <kbd>Main Page</kbd> with the reason <kbd>Preparing for move</kbd>.",
+
+ "apihelp-disabled-description": "This module has been disabled.",
+
+ "apihelp-edit-description": "Create and edit pages.",
+ "apihelp-edit-param-title": "Title of the page to edit. Cannot be used together with <var>$1pageid</var>.",
+ "apihelp-edit-param-pageid": "Page ID of the page to edit. Cannot be used together with <var>$1title</var>.",
+ "apihelp-edit-param-section": "Section number. <kbd>0</kbd> for the top section, <kbd>new</kbd> for a new section.",
+ "apihelp-edit-param-sectiontitle": "The title for a new section.",
+ "apihelp-edit-param-text": "Page content.",
+ "apihelp-edit-param-summary": "Edit summary. Also section title when $1section=new and $1sectiontitle is not set.",
+ "apihelp-edit-param-tags": "Change tags to apply to the revision.",
+ "apihelp-edit-param-minor": "Minor edit.",
+ "apihelp-edit-param-notminor": "Non-minor edit.",
+ "apihelp-edit-param-bot": "Mark this edit as bot.",
+ "apihelp-edit-param-basetimestamp": "Timestamp of the base revision, used to detect edit conflicts. May be obtained through [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
+ "apihelp-edit-param-starttimestamp": "Timestamp when the editing process began, used to detect edit conflicts. An appropriate value may be obtained using <var>[[Special:ApiHelp/main|curtimestamp]]</var> when beginning the edit process (e.g. when loading the page content to edit).",
+ "apihelp-edit-param-recreate": "Override any errors about the page having been deleted in the meantime.",
+ "apihelp-edit-param-createonly": "Don't edit the page if it exists already.",
+ "apihelp-edit-param-nocreate": "Throw an error if the page doesn't exist.",
+ "apihelp-edit-param-watch": "Add the page to the current user's watchlist.",
+ "apihelp-edit-param-unwatch": "Remove the page from the current user's watchlist.",
+ "apihelp-edit-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-edit-param-md5": "The MD5 hash of the $1text parameter, or the $1prependtext and $1appendtext parameters concatenated. If set, the edit won't be done unless the hash is correct.",
+ "apihelp-edit-param-prependtext": "Add this text to the beginning of the page. Overrides $1text.",
+ "apihelp-edit-param-appendtext": "Add this text to the end of the page. Overrides $1text.\n\nUse $1section=new to append a new section, rather than this parameter.",
+ "apihelp-edit-param-undo": "Undo this revision. Overrides $1text, $1prependtext and $1appendtext.",
+ "apihelp-edit-param-undoafter": "Undo all revisions from $1undo to this one. If not set, just undo one revision.",
+ "apihelp-edit-param-redirect": "Automatically resolve redirects.",
+ "apihelp-edit-param-contentformat": "Content serialization format used for the input text.",
+ "apihelp-edit-param-contentmodel": "Content model of the new content.",
+ "apihelp-edit-param-token": "The token should always be sent as the last parameter, or at least after the $1text parameter.",
+ "apihelp-edit-example-edit": "Edit a page.",
+ "apihelp-edit-example-prepend": "Prepend <kbd>_&#95;NOTOC_&#95;</kbd> to a page.",
+ "apihelp-edit-example-undo": "Undo revisions 13579 through 13585 with autosummary.",
+
+ "apihelp-emailuser-description": "Email a user.",
+ "apihelp-emailuser-param-target": "User to send email to.",
+ "apihelp-emailuser-param-subject": "Subject header.",
+ "apihelp-emailuser-param-text": "Mail body.",
+ "apihelp-emailuser-param-ccme": "Send a copy of this mail to me.",
+ "apihelp-emailuser-example-email": "Send an email to the User <kbd>WikiSysop</kbd> with the text <kbd>Content</kbd>.",
+
+ "apihelp-expandtemplates-description": "Expands all templates in wikitext.",
+ "apihelp-expandtemplates-param-title": "Title of page.",
+ "apihelp-expandtemplates-param-text": "Wikitext to convert.",
+ "apihelp-expandtemplates-param-revid": "Revision ID, for <nowiki>{{REVISIONID}}</nowiki> and similar variables.",
+ "apihelp-expandtemplates-param-prop": "Which pieces of information to get:\n;wikitext:The expanded wikitext.\n;categories:Any categories present in the input that are not represented in the wikitext output.\n;properties:Page properties defined by expanded magic words in the wikitext.\n;volatile:Whether the output is volatile and should not be reused elsewhere within the page.\n;ttl:The maximum time after which caches of the result should be invalidated.\n;parsetree:The XML parse tree of the input.\nNote that if no values are selected, the result will contain the wikitext, but the output will be in a deprecated format.",
+ "apihelp-expandtemplates-param-includecomments": "Whether to include HTML comments in the output.",
+ "apihelp-expandtemplates-param-generatexml": "Generate XML parse tree (replaced by $1prop=parsetree).",
+ "apihelp-expandtemplates-example-simple": "Expand the wikitext <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>.",
+
+ "apihelp-feedcontributions-description": "Returns a user contributions feed.",
+ "apihelp-feedcontributions-param-feedformat": "The format of the feed.",
+ "apihelp-feedcontributions-param-user": "What users to get the contributions for.",
+ "apihelp-feedcontributions-param-namespace": "Which namespace to filter the contributions by.",
+ "apihelp-feedcontributions-param-year": "From year (and earlier).",
+ "apihelp-feedcontributions-param-month": "From month (and earlier).",
+ "apihelp-feedcontributions-param-tagfilter": "Filter contributions that have these tags.",
+ "apihelp-feedcontributions-param-deletedonly": "Show only deleted contributions.",
+ "apihelp-feedcontributions-param-toponly": "Only show edits that are latest revisions.",
+ "apihelp-feedcontributions-param-newonly": "Only show edits that are page creations.",
+ "apihelp-feedcontributions-param-showsizediff": "Show the size difference between revisions.",
+ "apihelp-feedcontributions-example-simple": "Return contributions for user <kbd>Example</kbd>.",
+
+ "apihelp-feedrecentchanges-description": "Returns a recent changes feed.",
+ "apihelp-feedrecentchanges-param-feedformat": "The format of the feed.",
+ "apihelp-feedrecentchanges-param-namespace": "Namespace to limit the results to.",
+ "apihelp-feedrecentchanges-param-invert": "All namespaces but the selected one.",
+ "apihelp-feedrecentchanges-param-associated": "Include associated (talk or main) namespace.",
+ "apihelp-feedrecentchanges-param-days": "Days to limit the results to.",
+ "apihelp-feedrecentchanges-param-limit": "Maximum number of results to return.",
+ "apihelp-feedrecentchanges-param-from": "Show changes since then.",
+ "apihelp-feedrecentchanges-param-hideminor": "Hide minor changes.",
+ "apihelp-feedrecentchanges-param-hidebots": "Hide changes made by bots.",
+ "apihelp-feedrecentchanges-param-hideanons": "Hide changes made by anonymous users.",
+ "apihelp-feedrecentchanges-param-hideliu": "Hide changes made by registered users.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Hide patrolled changes.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Hide changes made by the current user.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filter by tag.",
+ "apihelp-feedrecentchanges-param-target": "Show only changes on pages linked from this page.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Show changes on pages linked to the selected page instead.",
+ "apihelp-feedrecentchanges-example-simple": "Show recent changes.",
+ "apihelp-feedrecentchanges-example-30days": "Show recent changes for 30 days.",
+
+ "apihelp-feedwatchlist-description": "Returns a watchlist feed.",
+ "apihelp-feedwatchlist-param-feedformat": "The format of the feed.",
+ "apihelp-feedwatchlist-param-hours": "List pages modified within this many hours from now.",
+ "apihelp-feedwatchlist-param-linktosections": "Link directly to changed sections if possible.",
+ "apihelp-feedwatchlist-example-default": "Show the watchlist feed.",
+ "apihelp-feedwatchlist-example-all6hrs": "Show all changes to watched pages in the past 6 hours.",
+
+ "apihelp-filerevert-description": "Revert a file to an old version.",
+ "apihelp-filerevert-param-filename": "Target filename, without the File: prefix.",
+ "apihelp-filerevert-param-comment": "Upload comment.",
+ "apihelp-filerevert-param-archivename": "Archive name of the revision to revert to.",
+ "apihelp-filerevert-example-revert": "Revert <kbd>Wiki.png</kbd> to the version of <kbd>2011-03-05T15:27:40Z</kbd>.",
+
+ "apihelp-help-description": "Display help for the specified modules.",
+ "apihelp-help-param-modules": "Modules to display help for (values of the <var>action</var> and <var>format</var> parameters, or <kbd>main</kbd>). Can specify submodules with a <kbd>+</kbd>.",
+ "apihelp-help-param-submodules": "Include help for submodules of the named module.",
+ "apihelp-help-param-recursivesubmodules": "Include help for submodules recursively.",
+ "apihelp-help-param-helpformat": "Format of the help output.",
+ "apihelp-help-param-wrap": "Wrap the output in a standard API response structure.",
+ "apihelp-help-param-toc": "Include a table of contents in the HTML output.",
+ "apihelp-help-example-main": "Help for the main module.",
+ "apihelp-help-example-recursive": "All help in one page.",
+ "apihelp-help-example-help": "Help for the help module itself.",
+ "apihelp-help-example-query": "Help for two query submodules.",
+
+ "apihelp-imagerotate-description": "Rotate one or more images.",
+ "apihelp-imagerotate-param-rotation": "Degrees to rotate image clockwise.",
+ "apihelp-imagerotate-example-simple": "Rotate <kbd>File:Example.png</kbd> by <kbd>90</kbd> degrees.",
+ "apihelp-imagerotate-example-generator": "Rotate all images in <kbd>Category:Flip</kbd> by <kbd>180</kbd> degrees.",
+
+ "apihelp-import-description": "Import a page from another wiki, or an XML file.\n\nNote that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when sending a file for the <var>xml</var> parameter.",
+ "apihelp-import-param-summary": "Import summary.",
+ "apihelp-import-param-xml": "Uploaded XML file.",
+ "apihelp-import-param-interwikisource": "For interwiki imports: wiki to import from.",
+ "apihelp-import-param-interwikipage": "For interwiki imports: page to import.",
+ "apihelp-import-param-fullhistory": "For interwiki imports: import the full history, not just the current version.",
+ "apihelp-import-param-templates": "For interwiki imports: import all included templates as well.",
+ "apihelp-import-param-namespace": "For interwiki imports: import to this namespace.",
+ "apihelp-import-param-rootpage": "Import as subpage of this page.",
+ "apihelp-import-example-import": "Import [[meta:Help:Parserfunctions]] to namespace 100 with full history.",
+
+ "apihelp-login-description": "Log in and get authentication cookies.\n\nIn the event of a successful log-in, the needed cookies will be included in the HTTP response headers. In the event of a failed log-in, further attempts may be throttled to limit automated password guessing attacks.",
+ "apihelp-login-param-name": "User name.",
+ "apihelp-login-param-password": "Password.",
+ "apihelp-login-param-domain": "Domain (optional).",
+ "apihelp-login-param-token": "Login token obtained in first request.",
+ "apihelp-login-example-gettoken": "Retrieve a login token.",
+ "apihelp-login-example-login": "Log in.",
+
+ "apihelp-logout-description": "Log out and clear session data.",
+ "apihelp-logout-example-logout": "Log the current user out.",
+
+ "apihelp-managetags-description": "Perform management tasks relating to change tags.",
+ "apihelp-managetags-param-operation": "Which operation to perform:\n;create:Create a new change tag for manual use.\n;delete:Remove a change tag from the database, including removing the tag from all revisions, recent change entries and log entries on which it is used.\n;activate:Activate a change tag, allowing users to apply it manually.\n;deactivate:Deactivate a change tag, preventing users from applying it manually.",
+ "apihelp-managetags-param-tag": "Tag to create, delete, activate or deactivate. For tag creation, the tag must not exist. For tag deletion, the tag must exist. For tag activation, the tag must exist and not be in use by an extension. For tag deactivation, the tag must be currently active and manually defined.",
+ "apihelp-managetags-param-reason": "An optional reason for creating, deleting, activating or deactivating the tag.",
+ "apihelp-managetags-param-ignorewarnings": "Whether to ignore any warnings that are issued during the operation.",
+ "apihelp-managetags-example-create": "Create a tag named <kbd>spam</kbd> with the reason <kbd>For use in edit patrolling</kbd>",
+ "apihelp-managetags-example-delete": "Delete the <kbd>vandlaism</kbd> tag with the reason <kbd>Misspelt</kbd>",
+ "apihelp-managetags-example-activate": "Activate a tag named <kbd>spam</kbd> with the reason <kbd>For use in edit patrolling</kbd>",
+ "apihelp-managetags-example-deactivate": "Deactivate a tag named <kbd>spam</kbd> with the reason <kbd>No longer required</kbd>",
+
+ "apihelp-move-description": "Move a page.",
+ "apihelp-move-param-from": "Title of the page to rename. Cannot be used together with <var>$1fromid</var>.",
+ "apihelp-move-param-fromid": "Page ID of the page to rename. Cannot be used together with <var>$1from</var>.",
+ "apihelp-move-param-to": "Title to rename the page to.",
+ "apihelp-move-param-reason": "Reason for the rename.",
+ "apihelp-move-param-movetalk": "Rename the talk page, if it exists.",
+ "apihelp-move-param-movesubpages": "Rename subpages, if applicable.",
+ "apihelp-move-param-noredirect": "Don't create a redirect.",
+ "apihelp-move-param-watch": "Add the page and the redirect to the current user's watchlist.",
+ "apihelp-move-param-unwatch": "Remove the page and the redirect from the current user's watchlist.",
+ "apihelp-move-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-move-param-ignorewarnings": "Ignore any warnings.",
+ "apihelp-move-example-move": "Move <kbd>Badtitle</kbd> to <kbd>Goodtitle</kbd> without leaving a redirect.",
+
+ "apihelp-opensearch-description": "Search the wiki using the OpenSearch protocol.",
+ "apihelp-opensearch-param-search": "Search string.",
+ "apihelp-opensearch-param-limit": "Maximum number of results to return.",
+ "apihelp-opensearch-param-namespace": "Namespaces to search.",
+ "apihelp-opensearch-param-suggest": "Do nothing if <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> is false.",
+ "apihelp-opensearch-param-redirects": "How to handle redirects:\n;return:Return the redirect itself.\n;resolve:Return the target page. May return fewer than $1limit results.\nFor historical reasons, the default is \"return\" for $1format=json and \"resolve\" for other formats.",
+ "apihelp-opensearch-param-format": "The format of the output.",
+ "apihelp-opensearch-param-warningsaserror": "If warnings are raised with <kbd>format=json</kbd>, return an API error instead of ignoring them.",
+ "apihelp-opensearch-example-te": "Find pages beginning with <kbd>Te</kbd>.",
+
+ "apihelp-options-description": "Change preferences of the current user.\n\nOnly options which are registered in core or in one of installed extensions, or options with keys prefixed with \"userjs-\" (intended to be used by user scripts), can be set.",
+ "apihelp-options-param-reset": "Resets preferences to the site defaults.",
+ "apihelp-options-param-resetkinds": "List of types of options to reset when the <var>$1reset</var> option is set.",
+ "apihelp-options-param-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.",
+ "apihelp-options-param-optionname": "A name of a option which should be set to the value given by <var>$1optionvalue</var>.",
+ "apihelp-options-param-optionvalue": "A value of the option specified by <var>$1optionname</var>, can contain pipe characters.",
+ "apihelp-options-example-reset": "Reset all preferences.",
+ "apihelp-options-example-change": "Change <kbd>skin</kbd> and <kbd>hideminor</kbd> preferences.",
+ "apihelp-options-example-complex": "Reset all preferences, then set <kbd>skin</kbd> and <kbd>nickname</kbd>.",
+
+ "apihelp-paraminfo-description": "Obtain information about API modules.",
+ "apihelp-paraminfo-param-modules": "List of module names (values of the <var>action</var> and <var>format</var> parameters, or <kbd>main</kbd>). Can specify submodules with a <kbd>+</kbd>.",
+ "apihelp-paraminfo-param-helpformat": "Format of help strings.",
+ "apihelp-paraminfo-param-querymodules": "List of query module names (value of <var>prop</var>, <var>meta</var> or <var>list</var> parameter). Use <kbd>$1modules=query+foo</kbd> instead of <kbd>$1querymodules=foo</kbd>.",
+ "apihelp-paraminfo-param-mainmodule": "Get information about the main (top-level) module as well. Use <kbd>$1modules=main</kbd> instead.",
+ "apihelp-paraminfo-param-pagesetmodule": "Get information about the pageset module (providing titles= and friends) as well.",
+ "apihelp-paraminfo-param-formatmodules": "List of format module names (value of <var>format</var> parameter). Use <var>$1modules</var> instead.",
+ "apihelp-paraminfo-example-1": "Show info for <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, and <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+
+ "apihelp-parse-description": "Parses content and returns parser output.\n\nSee the various prop-modules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd> to get information from the current version of a page.\n\nThere are several ways to specify the text to parse:\n# Specify a page or revision, using <var>$1page</var>, <var>$1pageid</var>, or <var>$1oldid</var>.\n# Specify content explicitly, using <var>$1text</var>, <var>$1title</var>, and <var>$1contentmodel</var>.\n# Specify only a summary to parse. <var>$1prop</var> should be given an empty value.",
+ "apihelp-parse-param-title": "Title of page the text belongs to. If omitted, <var>$1contentmodel</var> must be specified, and [[API]] will be used as the title.",
+ "apihelp-parse-param-text": "Text to parse. Use <var>$1title</var> or <var>$1contentmodel</var> to control the content model.",
+ "apihelp-parse-param-summary": "Summary to parse.",
+ "apihelp-parse-param-page": "Parse the content of this page. Cannot be used together with <var>$1text</var> and <var>$1title</var>.",
+ "apihelp-parse-param-pageid": "Parse the content of this page. Overrides <var>$1page</var>.",
+ "apihelp-parse-param-redirects": "If <var>$1page</var> or <var>$1pageid</var> is set to a redirect, resolve it.",
+ "apihelp-parse-param-oldid": "Parse the content of this revision. Overrides <var>$1page</var> and <var>$1pageid</var>.",
+ "apihelp-parse-param-prop": "Which pieces of information to get:\n;text:Gives the parsed text of the wikitext.\n;langlinks:Gives the language links in the parsed wikitext.\n;categories:Gives the categories in the parsed wikitext.\n;categorieshtml:Gives the HTML version of the categories.\n;links:Gives the internal links in the parsed wikitext.\n;templates:Gives the templates in the parsed wikitext.\n;images:Gives the images in the parsed wikitext.\n;externallinks:Gives the external links in the parsed wikitext.\n;sections:Gives the sections in the parsed wikitext.\n;revid:Adds the revision ID of the parsed page.\n;displaytitle:Adds the title of the parsed wikitext.\n;headitems:Gives items to put in the &lt;head&gt; of the page.\n;headhtml:Gives parsed &lt;head&gt; of the page.\n;modules:Gives the ResourceLoader modules used on the page.\n;indicators:Gives the HTML of page status indicators used on the page.\n;iwlinks:Gives interwiki links in the parsed wikitext.\n;wikitext:Gives the original wikitext that was parsed.\n;properties:Gives various properties defined in the parsed wikitext.\n;limitreportdata:Gives the limit report in a structured way. Gives no data, when $1disablepp is set.\n;limitreporthtml:Gives the HTML version of the limit report. Gives no data, when $1disablepp is set.",
+ "apihelp-parse-param-pst": "Do a pre-save transform on the input before parsing it. Only valid when used with text.",
+ "apihelp-parse-param-onlypst": "Do a pre-save transform (PST) on the input, but don't parse it. Returns the same wikitext, after a PST has been applied. Only valid when used with <var>$1text</var>.",
+ "apihelp-parse-param-effectivelanglinks": "Includes language links supplied by extensions (for use with <kbd>$1prop=langlinks</kbd>).",
+ "apihelp-parse-param-section": "Only retrieve the content of this section number or when <kbd>new</kbd> generate a new section.\n\n<kbd>new</kbd> section is only honored when specifying <var>text</var>.",
+ "apihelp-parse-param-sectiontitle": "New section title when <var>section</var> is <kbd>new</kbd>.\n\nUnlike page editing, this does not fall back to <var>summary</var> when omitted or empty.",
+ "apihelp-parse-param-disablepp": "Disable the PP Report from the parser output.",
+ "apihelp-parse-param-disableeditsection": "Disable edit section links from the parser output.",
+ "apihelp-parse-param-generatexml": "Generate XML parse tree (requires content model <code>$1</code>).",
+ "apihelp-parse-param-preview": "Parse in preview mode.",
+ "apihelp-parse-param-sectionpreview": "Parse in section preview mode (enables preview mode too).",
+ "apihelp-parse-param-disabletoc": "Disable table of contents in output.",
+ "apihelp-parse-param-contentformat": "Content serialization format used for the input text. Only valid when used with $1text.",
+ "apihelp-parse-param-contentmodel": "Content model of the input text. If omitted, $1title must be specified, and default will be the model of the specified title. Only valid when used with $1text.",
+ "apihelp-parse-example-page": "Parse a page.",
+ "apihelp-parse-example-text": "Parse wikitext.",
+ "apihelp-parse-example-texttitle": "Parse wikitext, specifying the page title.",
+ "apihelp-parse-example-summary": "Parse a summary.",
+
+ "apihelp-patrol-description": "Patrol a page or revision.",
+ "apihelp-patrol-param-rcid": "Recentchanges ID to patrol.",
+ "apihelp-patrol-param-revid": "Revision ID to patrol.",
+ "apihelp-patrol-example-rcid": "Patrol a recent change.",
+ "apihelp-patrol-example-revid": "Patrol a revision.",
+
+ "apihelp-protect-description": "Change the protection level of a page.",
+ "apihelp-protect-param-title": "Title of the page to (un)protect. Cannot be used together with $1pageid.",
+ "apihelp-protect-param-pageid": "ID of the page to (un)protect. Cannot be used together with $1title.",
+ "apihelp-protect-param-protections": "List of protection levels, formatted <kbd>action=level</kbd> (e.g. <kbd>edit=sysop</kbd>).\n\n<strong>Note:</strong> Any actions not listed will have restrictions removed.",
+ "apihelp-protect-param-expiry": "Expiry timestamps. If only one timestamp is set, it'll be used for all protections. Use <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd>, or <kbd>never</kbd>, for a never-expiring protection.",
+ "apihelp-protect-param-reason": "Reason for (un)protecting.",
+ "apihelp-protect-param-cascade": "Enable cascading protection (i.e. protect pages included in this page). Ignored if all protection levels given do not support cascading.",
+ "apihelp-protect-param-watch": "If set, add the page being (un)protected to the current user's watchlist.",
+ "apihelp-protect-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-protect-example-protect": "Protect a page.",
+ "apihelp-protect-example-unprotect": "Unprotect a page by setting restrictions to <kbd>all</kbd>.",
+ "apihelp-protect-example-unprotect2": "Unprotect a page by setting no restrictions.",
+
+ "apihelp-purge-description": "Purge the cache for the given titles.\n\nRequires a POST request if the user is not logged in.",
+ "apihelp-purge-param-forcelinkupdate": "Update the links tables.",
+ "apihelp-purge-param-forcerecursivelinkupdate": "Update the links table, and update the links tables for any page that uses this page as a template.",
+ "apihelp-purge-example-simple": "Purge the <kbd>Main Page</kbd> and the <kbd>API</kbd> page.",
+ "apihelp-purge-example-generator": "Purge the first 10 pages in the main namespace.",
+
+ "apihelp-query-description": "Fetch data from and about MediaWiki.\n\nAll data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.",
+ "apihelp-query-param-prop": "Which properties to get for the queried pages.",
+ "apihelp-query-param-list": "Which lists to get.",
+ "apihelp-query-param-meta": "Which metadata to get.",
+ "apihelp-query-param-indexpageids": "Include an additional pageids section listing all returned page IDs.",
+ "apihelp-query-param-export": "Export the current revisions of all given or generated pages.",
+ "apihelp-query-param-exportnowrap": "Return the export XML without wrapping it in an XML result (same format as [[Special:Export]]). Can only be used with $1export.",
+ "apihelp-query-param-iwurl": "Whether to get the full URL if the title is an interwiki link.",
+ "apihelp-query-param-continue": "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.\n\nThis parameter is recommended for all new development, and will be made default in the next API version.",
+ "apihelp-query-param-rawcontinue": "Currently ignored. In the future, <var>$1continue</var> will become the default and this will be needed to receive the raw <samp>query-continue</samp> data.",
+ "apihelp-query-example-revisions": "Fetch [[Special:ApiHelp/query+siteinfo|site info]] and [[Special:ApiHelp/query+revisions|revisions]] of <kbd>Main Page</kbd>.",
+ "apihelp-query-example-allpages": "Fetch revisions of pages beginning with <kbd>API/</kbd>.",
+
+ "apihelp-query+allcategories-description": "Enumerate all categories.",
+ "apihelp-query+allcategories-param-from": "The category to start enumerating from.",
+ "apihelp-query+allcategories-param-to": "The category to stop enumerating at.",
+ "apihelp-query+allcategories-param-prefix": "Search for all category titles that begin with this value.",
+ "apihelp-query+allcategories-param-dir": "Direction to sort in.",
+ "apihelp-query+allcategories-param-min": "Only return categories with at least this many members.",
+ "apihelp-query+allcategories-param-max": "Only return categories with at most this many members.",
+ "apihelp-query+allcategories-param-limit": "How many categories to return.",
+ "apihelp-query+allcategories-param-prop": "Which properties to get:\n;size:Adds number of pages in the category.\n;hidden:Tags categories that are hidden with _&#95;HIDDENCAT_&#95;.",
+ "apihelp-query+allcategories-example-size": "List categories with information on the number of pages in each.",
+ "apihelp-query+allcategories-example-generator": "Retrieve info about the category page itself for categories beginning <kbd>List</kbd>.",
+
+ "apihelp-query+alldeletedrevisions-description": "List all deleted revisions by a user or in a namespace.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "May only be used with <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Cannot be used with <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-param-start": "The timestamp to start enumerating from.",
+ "apihelp-query+alldeletedrevisions-param-end": "The timestamp to stop enumerating at.",
+ "apihelp-query+alldeletedrevisions-param-from": "Start listing at this title.",
+ "apihelp-query+alldeletedrevisions-param-to": "Stop listing at this title.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Search for all page titles that begin with this value.",
+ "apihelp-query+alldeletedrevisions-param-tag": "Only list revisions tagged with this tag.",
+ "apihelp-query+alldeletedrevisions-param-user": "Only list revisions by this user.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Don't list revisions by this user.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Only list pages in this namespace.",
+ "apihelp-query+alldeletedrevisions-param-miser-user-namespace": "<strong>Note:</strong> Due to [[mw:Manual:$wgMiserMode|miser mode]], using <var>$1user</var> and <var>$1namespace</var> together may result in fewer than <var>$1limit</var> results returned before continuing; in extreme cases, zero results may be returned.",
+ "apihelp-query+alldeletedrevisions-param-generatetitles": "When being used as a generator, generate titles rather than revision IDs.",
+ "apihelp-query+alldeletedrevisions-example-user": "List the last 50 deleted contributions by user <kbd>Example<kbd>.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "List the first 50 deleted revisions in the main namespace.",
+
+ "apihelp-query+allfileusages-description": "List all file usages, including non-existing.",
+ "apihelp-query+allfileusages-param-from": "The title of the file to start enumerating from.",
+ "apihelp-query+allfileusages-param-to": "The title of the file to stop enumerating at.",
+ "apihelp-query+allfileusages-param-prefix": "Search for all file titles that begin with this value.",
+ "apihelp-query+allfileusages-param-unique": "Only show distinct file titles. Cannot be used with $1prop=ids.\nWhen used as a generator, yields target pages instead of source pages.",
+ "apihelp-query+allfileusages-param-prop": "Which pieces of information to include:\n;ids:Adds the page ID of the using page (cannot be used with $1unique).\n;title:Adds the title of the file.",
+ "apihelp-query+allfileusages-param-limit": "How many total items to return.",
+ "apihelp-query+allfileusages-param-dir": "The direction in which to list.",
+ "apihelp-query+allfileusages-example-B": "List file titles, including missing ones, with page IDs they are from, starting at <kbd>B</kbd>.",
+ "apihelp-query+allfileusages-example-unique": "List unique file titles.",
+ "apihelp-query+allfileusages-example-unique-generator": "Gets all file titles, marking the missing ones.",
+ "apihelp-query+allfileusages-example-generator": "Gets pages containing the files.",
+
+ "apihelp-query+allimages-description": "Enumerate all images sequentially.",
+ "apihelp-query+allimages-param-sort": "Property to sort by.",
+ "apihelp-query+allimages-param-dir": "The direction in which to list.",
+ "apihelp-query+allimages-param-from": "The image title to start enumerating from. Can only be used with $1sort=name.",
+ "apihelp-query+allimages-param-to": "The image title to stop enumerating at. Can only be used with $1sort=name.",
+ "apihelp-query+allimages-param-start": "The timestamp to start enumerating from. Can only be used with $1sort=timestamp.",
+ "apihelp-query+allimages-param-end": "The timestamp to end enumerating. Can only be used with $1sort=timestamp.",
+ "apihelp-query+allimages-param-prefix": "Search for all image titles that begin with this value. Can only be used with $1sort=name.",
+ "apihelp-query+allimages-param-minsize": "Limit to images with at least this many bytes.",
+ "apihelp-query+allimages-param-maxsize": "Limit to images with at most this many bytes.",
+ "apihelp-query+allimages-param-sha1": "SHA1 hash of image. Overrides $1sha1base36.",
+ "apihelp-query+allimages-param-sha1base36": "SHA1 hash of image in base 36 (used in MediaWiki).",
+ "apihelp-query+allimages-param-user": "Only return files uploaded by this user. Can only be used with $1sort=timestamp. Cannot be used together with $1filterbots.",
+ "apihelp-query+allimages-param-filterbots": "How to filter files uploaded by bots. Can only be used with $1sort=timestamp. Cannot be used together with $1user.",
+ "apihelp-query+allimages-param-mime": "What MIME types to search for, e.g. <kbd>image/jpeg</kbd>.",
+ "apihelp-query+allimages-param-limit": "How many images in total to return.",
+ "apihelp-query+allimages-example-B": "Show a list of files starting at the letter <kbd>B</kbd>.",
+ "apihelp-query+allimages-example-recent": "Show a list of recently uploaded files, similar to [[Special:NewFiles]].",
+ "apihelp-query+allimages-example-mimetypes": "Show a list of files with MIME type <kbd>image/png</kbd> or <kbd>image/gif</kbd>",
+ "apihelp-query+allimages-example-generator": "Show info about 4 files starting at the letter <kbd>T</kbd>.",
+
+ "apihelp-query+alllinks-description": "Enumerate all links that point to a given namespace.",
+ "apihelp-query+alllinks-param-from": "The title of the link to start enumerating from.",
+ "apihelp-query+alllinks-param-to": "The title of the link to stop enumerating at.",
+ "apihelp-query+alllinks-param-prefix": "Search for all linked titles that begin with this value.",
+ "apihelp-query+alllinks-param-unique": "Only show distinct linked titles. Cannot be used with <kbd>$1prop=ids</kbd>.\nWhen used as a generator, yields target pages instead of source pages.",
+ "apihelp-query+alllinks-param-prop": "Which pieces of information to include:\n;ids:Adds the page ID of the linking page (cannot be used with <var>$1unique</var>).\n;title:Adds the title of the link.",
+ "apihelp-query+alllinks-param-namespace": "The namespace to enumerate.",
+ "apihelp-query+alllinks-param-limit": "How many total items to return.",
+ "apihelp-query+alllinks-param-dir": "The direction in which to list.",
+ "apihelp-query+alllinks-example-B": "List linked titles, including missing ones, with page IDs they are from, starting at <kbd>B</kbd>.",
+ "apihelp-query+alllinks-example-unique": "List unique linked titles.",
+ "apihelp-query+alllinks-example-unique-generator": "Gets all linked titles, marking the missing ones.",
+ "apihelp-query+alllinks-example-generator": "Gets pages containing the links.",
+
+ "apihelp-query+allmessages-description": "Return messages from this site.",
+ "apihelp-query+allmessages-param-messages": "Which messages to output. <kbd>*</kbd> (default) means all messages.",
+ "apihelp-query+allmessages-param-prop": "Which properties to get.",
+ "apihelp-query+allmessages-param-enableparser": "Set to enable parser, will preprocess the wikitext of message (substitute magic words, handle templates, etc.).",
+ "apihelp-query+allmessages-param-nocontent": "If set, do not include the content of the messages in the output.",
+ "apihelp-query+allmessages-param-includelocal": "Also include local messages, i.e. messages that don't exist in the software but do exist as a MediaWiki: page.\nThis lists all MediaWiki: pages, so it will also list those that aren't really messages such as [[MediaWiki:Common.js|Common.js]].",
+ "apihelp-query+allmessages-param-args": "Arguments to be substituted into message.",
+ "apihelp-query+allmessages-param-filter": "Return only messages with names that contain this string.",
+ "apihelp-query+allmessages-param-customised": "Return only messages in this customisation state.",
+ "apihelp-query+allmessages-param-lang": "Return messages in this language.",
+ "apihelp-query+allmessages-param-from": "Return messages starting at this message.",
+ "apihelp-query+allmessages-param-to": "Return messages ending at this message.",
+ "apihelp-query+allmessages-param-title": "Page name to use as context when parsing message (for $1enableparser option).",
+ "apihelp-query+allmessages-param-prefix": "Return messages with this prefix.",
+ "apihelp-query+allmessages-example-ipb": "Show messages starting with <kbd>ipb-</kbd>.",
+ "apihelp-query+allmessages-example-de": "Show messages <kbd>august</kbd> and <kbd>mainpage</kbd> in German.",
+
+ "apihelp-query+allpages-description": "Enumerate all pages sequentially in a given namespace.",
+ "apihelp-query+allpages-param-from": "The page title to start enumerating from.",
+ "apihelp-query+allpages-param-to": "The page title to stop enumerating at.",
+ "apihelp-query+allpages-param-prefix": "Search for all page titles that begin with this value.",
+ "apihelp-query+allpages-param-namespace": "The namespace to enumerate.",
+ "apihelp-query+allpages-param-filterredir": "Which pages to list.",
+ "apihelp-query+allpages-param-minsize": "Limit to pages with at least this many bytes.",
+ "apihelp-query+allpages-param-maxsize": "Limit to pages with at most this many bytes.",
+ "apihelp-query+allpages-param-prtype": "Limit to protected pages only.",
+ "apihelp-query+allpages-param-prlevel": "Filter protections based on protection level (must be used with $1prtype= parameter).",
+ "apihelp-query+allpages-param-prfiltercascade": "Filter protections based on cascadingness (ignored when $1prtype isn't set).",
+ "apihelp-query+allpages-param-limit": "How many total pages to return.",
+ "apihelp-query+allpages-param-dir": "The direction in which to list.",
+ "apihelp-query+allpages-param-filterlanglinks": "Filter based on whether a page has langlinks. Note that this may not consider langlinks added by extensions.",
+ "apihelp-query+allpages-param-prexpiry": "Which protection expiry to filter the page on:\n;indefinite:Get only pages with indefinite protection expiry.\n;definite:Get only pages with a definite (specific) protection expiry.\n;all:Get pages with any protections expiry.",
+ "apihelp-query+allpages-example-B": "Show a list of pages starting at the letter <kbd>B</kbd>.",
+ "apihelp-query+allpages-example-generator": "Show info about 4 pages starting at the letter <kbd>T</kbd>.",
+ "apihelp-query+allpages-example-generator-revisions": "Show content of first 2 non-redirect pages beginning at <kbd>Re</kbd>.",
+
+ "apihelp-query+allredirects-description": "List all redirects to a namespace.",
+ "apihelp-query+allredirects-param-from": "The title of the redirect to start enumerating from.",
+ "apihelp-query+allredirects-param-to": "The title of the redirect to stop enumerating at.",
+ "apihelp-query+allredirects-param-prefix": "Search for all target pages that begin with this value.",
+ "apihelp-query+allredirects-param-unique": "Only show distinct target pages. Cannot be used with $1prop=ids|fragment|interwiki.\nWhen used as a generator, yields target pages instead of source pages.",
+ "apihelp-query+allredirects-param-prop": "Which pieces of information to include:\n;ids:Adds the page ID of the redirecting page (cannot be used with <var>$1unique</var>).\n;title:Adds the title of the redirect.\n;fragment:Adds the fragment from the redirect, if any (cannot be used with <var>$1unique</var>).\n;interwiki:Adds the interwiki prefix from the redirect, if any (cannot be used with <var>$1unique</var>).",
+ "apihelp-query+allredirects-param-namespace": "The namespace to enumerate.",
+ "apihelp-query+allredirects-param-limit": "How many total items to return.",
+ "apihelp-query+allredirects-param-dir": "The direction in which to list.",
+ "apihelp-query+allredirects-example-B": "List target pages, including missing ones, with page IDs they are from, starting at <kbd>B</kbd>.",
+ "apihelp-query+allredirects-example-unique": "List unique target pages.",
+ "apihelp-query+allredirects-example-unique-generator": "Gets all target pages, marking the missing ones.",
+ "apihelp-query+allredirects-example-generator": "Gets pages containing the redirects.",
+
+ "apihelp-query+alltransclusions-description": "List all transclusions (pages embedded using &#123;&#123;x&#125;&#125;), including non-existing.",
+ "apihelp-query+alltransclusions-param-from": "The title of the transclusion to start enumerating from.",
+ "apihelp-query+alltransclusions-param-to": "The title of the transclusion to stop enumerating at.",
+ "apihelp-query+alltransclusions-param-prefix": "Search for all transcluded titles that begin with this value.",
+ "apihelp-query+alltransclusions-param-unique": "Only show distinct transcluded titles. Cannot be used with $1prop=ids.\nWhen used as a generator, yields target pages instead of source pages.",
+ "apihelp-query+alltransclusions-param-prop": "Which pieces of information to include:\n;ids:Adds the page ID of the transcluding page (cannot be used with $1unique).\n;title:Adds the title of the transclusion.",
+ "apihelp-query+alltransclusions-param-namespace": "The namespace to enumerate.",
+ "apihelp-query+alltransclusions-param-limit": "How many total items to return.",
+ "apihelp-query+alltransclusions-param-dir": "The direction in which to list.",
+ "apihelp-query+alltransclusions-example-B": "List transcluded titles, including missing ones, with page IDs they are from, starting at <kbd>B</kbd>.",
+ "apihelp-query+alltransclusions-example-unique": "List unique transcluded titles.",
+ "apihelp-query+alltransclusions-example-unique-generator": "Gets all transcluded titles, marking the missing ones.",
+ "apihelp-query+alltransclusions-example-generator": "Gets pages containing the transclusions.",
+
+ "apihelp-query+allusers-description": "Enumerate all registered users.",
+ "apihelp-query+allusers-param-from": "The user name to start enumerating from.",
+ "apihelp-query+allusers-param-to": "The user name to stop enumerating at.",
+ "apihelp-query+allusers-param-prefix": "Search for all users that begin with this value.",
+ "apihelp-query+allusers-param-dir": "Direction to sort in.",
+ "apihelp-query+allusers-param-group": "Only include users in the given groups.",
+ "apihelp-query+allusers-param-excludegroup": "Exclude users in the given groups.",
+ "apihelp-query+allusers-param-rights": "Only include users with the given rights. Does not include rights granted by implicit or auto-promoted groups like *, user, or autoconfirmed.",
+ "apihelp-query+allusers-param-prop": "Which pieces of information to include:\n;blockinfo:Adds the information about a current block on the user.\n;groups:Lists groups that the user is in. This uses more server resources and may return fewer results than the limit.\n;implicitgroups:Lists all the groups the user is automatically in.\n;rights:Lists rights that the user has.\n;editcount:Adds the edit count of the user.\n;registration:Adds the timestamp of when the user registered if available (may be blank).",
+ "apihelp-query+allusers-param-limit": "How many total user names to return.",
+ "apihelp-query+allusers-param-witheditsonly": "Only list users who have made edits.",
+ "apihelp-query+allusers-param-activeusers": "Only list users active in the last $1 {{PLURAL:$1|day|days}}.",
+ "apihelp-query+allusers-example-Y": "List users starting at <kbd>Y</kbd>.",
+
+ "apihelp-query+backlinks-description": "Find all pages that link to the given page.",
+ "apihelp-query+backlinks-param-title": "Title to search. Cannot be used together with <var>$1pageid</var>.",
+ "apihelp-query+backlinks-param-pageid": "Page ID to search. Cannot be used together with <var>$1title</var>.",
+ "apihelp-query+backlinks-param-namespace": "The namespace to enumerate.",
+ "apihelp-query+backlinks-param-dir": "The direction in which to list.",
+ "apihelp-query+backlinks-param-filterredir": "How to filter for redirects. If set to <kbd>nonredirects</kbd> when <var>$1redirect</var> is enabled, this is only applied to the second level.",
+ "apihelp-query+backlinks-param-limit": "How many total pages to return. If <var>$1redirect</var> is enabled, limit applies to each level separately (which means up to 2 * <var>$1limit</var> results may be returned).",
+ "apihelp-query+backlinks-param-redirect": "If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.",
+ "apihelp-query+backlinks-example-simple": "Show links to <kbd>Main page<kbd>.",
+ "apihelp-query+backlinks-example-generator": "Get information about pages linking to <kbd>Main page<kbd>.",
+
+ "apihelp-query+blocks-description": "List all blocked users and IP addresses.",
+ "apihelp-query+blocks-param-start": "The timestamp to start enumerating from.",
+ "apihelp-query+blocks-param-end": "The timestamp to stop enumerating at.",
+ "apihelp-query+blocks-param-ids": "List of block IDs to list (optional).",
+ "apihelp-query+blocks-param-users": "List of users to search for (optional).",
+ "apihelp-query+blocks-param-ip": "Get all blocks applying to this IP or CIDR range, including range blocks.\nCannot be used together with <var>$3users</var>. CIDR ranges broader than IPv4/$1 or IPv6/$2 are not accepted.",
+ "apihelp-query+blocks-param-limit": "The maximum number of blocks to list.",
+ "apihelp-query+blocks-param-prop": "Which properties to get:\n;id:Adds the ID of the block.\n;user:Adds the username of the blocked user.\n;userid:Adds the user ID of the blocked user.\n;by:Adds the username of the blocking user.\n;byid:Adds the user ID of the blocking user.\n;timestamp:Adds the timestamp of when the block was given.\n;expiry:Adds the timestamp of when the block expires.\n;reason:Adds the reason given for the block.\n;range:Adds the range of IP addresses affected by the block.\n;flags:Tags the ban with (autoblock, anononly, etc.).",
+ "apihelp-query+blocks-param-show": "Show only items that meet these criteria.\nFor example, to see only indefinite blocks on IP addresses, set <kbd>$1show=ip|!temp</kbd>.",
+ "apihelp-query+blocks-example-simple": "List blocks.",
+ "apihelp-query+blocks-example-users": "List blocks of users <kbd>Alice</kbd> and <kbd>Bob</kbd>.",
+
+ "apihelp-query+categories-description": "List all categories the pages belong to.",
+ "apihelp-query+categories-param-prop": "Which additional properties to get for each category:\n;sortkey:Adds the sortkey (hexadecimal string) and sortkey prefix (human-readable part) for the category.\n;timestamp:Adds timestamp of when the category was added.\n;hidden:Tags categories that are hidden with _&#95;HIDDENCAT_&#95;.",
+ "apihelp-query+categories-param-show": "Which kind of categories to show.",
+ "apihelp-query+categories-param-limit": "How many categories to return.",
+ "apihelp-query+categories-param-categories": "Only list these categories. Useful for checking whether a certain page is in a certain category.",
+ "apihelp-query+categories-param-dir": "The direction in which to list.",
+ "apihelp-query+categories-example-simple": "Get a list of categories the page <kbd>Albert Einstein</kbd> belongs to.",
+ "apihelp-query+categories-example-generator": "Get information about all categories used in the page <kbd>Albert Einstein</kbd>.",
+
+ "apihelp-query+categoryinfo-description": "Returns information about the given categories.",
+ "apihelp-query+categoryinfo-example-simple": "Get information about <kbd>Category:Foo</kbd> and <kbd>Category:Bar</kbd>.",
+
+ "apihelp-query+categorymembers-description": "List all pages in a given category.",
+ "apihelp-query+categorymembers-param-title": "Which category to enumerate (required). Must include the <kbd>{{ns:category}}:</kbd> prefix. Cannot be used together with <var>$1pageid</var>.",
+ "apihelp-query+categorymembers-param-pageid": "Page ID of the category to enumerate. Cannot be used together with <var>$1title</var>.",
+ "apihelp-query+categorymembers-param-prop": "Which pieces of information to include:\n;ids:Adds the page ID.\n;title:Adds the title and namespace ID of the page.\n;sortkey:Adds the sortkey used for sorting in the category (hexadecimal string).\n;sortkeyprefix:Adds the sortkey prefix used for sorting in the category (human-readable part of the sortkey).\n;type:Adds the type that the page has been categorised as (page, subcat or file).\n;timestamp:Adds the timestamp of when the page was included.",
+ "apihelp-query+categorymembers-param-namespace": "Only include pages in these namespaces. Note that <kbd>$1type=subcat</kbd> or <kbd>$1type=file</kbd> may be used instead of <kbd>$1namespace=14</kbd> or <kbd>6</kbd>.",
+ "apihelp-query+categorymembers-param-type": "Which type of category members to include. Ignored when <kbd>$1sort=timestamp</kbd> is set.",
+ "apihelp-query+categorymembers-param-limit": "The maximum number of pages to return.",
+ "apihelp-query+categorymembers-param-sort": "Property to sort by.",
+ "apihelp-query+categorymembers-param-dir": "In which direction to sort.",
+ "apihelp-query+categorymembers-param-start": "Timestamp to start listing from. Can only be used with <kbd>$1sort=timestamp</kbd>.",
+ "apihelp-query+categorymembers-param-end": "Timestamp to end listing at. Can only be used with <kbd>$1sort=timestamp</kbd>.",
+ "apihelp-query+categorymembers-param-starthexsortkey": "Sortkey to start listing from, as returned by <kbd>$1prop=sortkey</kbd>. Can only be used with <kbd>$1sort=sortkey</kbd>.",
+ "apihelp-query+categorymembers-param-endhexsortkey": "Sortkey to end listing from, as returned by <kbd>$1prop=sortkey</kbd>. Can only be used with <kbd>$1sort=sortkey</kbd>.",
+ "apihelp-query+categorymembers-param-startsortkeyprefix": "Sortkey prefix to start listing from. Can only be used with <kbd>$1sort=sortkey</kbd>. Overrides <var>$1starthexsortkey</var>.",
+ "apihelp-query+categorymembers-param-endsortkeyprefix": "Sortkey prefix to end listing BEFORE (not at, if this value occurs it will not be included!). Can only be used with $1sort=sortkey. Overrides $1endhexsortkey.",
+ "apihelp-query+categorymembers-param-startsortkey": "Use $1starthexsortkey instead.",
+ "apihelp-query+categorymembers-param-endsortkey": "Use $1endhexsortkey instead.",
+ "apihelp-query+categorymembers-example-simple": "Get first 10 pages in <kbd>Category:Physics</kbd>.",
+ "apihelp-query+categorymembers-example-generator": "Get page info about first 10 pages in <kbd>Category:Physics</kbd>.",
+
+ "apihelp-query+contributors-description": "Get the list of logged-in contributors and the count of anonymous contributors to a page.",
+ "apihelp-query+contributors-param-group": "Only include users in the given groups. Does not include implicit or auto-promoted groups like *, user, or autoconfirmed.",
+ "apihelp-query+contributors-param-excludegroup": "Exclude users in the given groups. Does not include implicit or auto-promoted groups like *, user, or autoconfirmed.",
+ "apihelp-query+contributors-param-rights": "Only include users having the given rights. Does not include rights granted by implicit or auto-promoted groups like *, user, or autoconfirmed.",
+ "apihelp-query+contributors-param-excluderights": "Exclude users having the given rights. Does not include rights granted by implicit or auto-promoted groups like *, user, or autoconfirmed.",
+ "apihelp-query+contributors-param-limit": "How many contributors to return.",
+ "apihelp-query+contributors-example-simple": "Show contributors to the page <kbd>Main Page</kbd>.",
+
+ "apihelp-query+deletedrevisions-description": "Get deleted revision information.\n\nMay be used in several ways:\n# Get deleted revisions for a set of pages, by setting titles or pageids. Ordered by title and timestamp.\n# Get data about a set of deleted revisions by setting their IDs with revids. Ordered by revision ID.",
+ "apihelp-query+deletedrevisions-param-start": "The timestamp to start enumerating from. Ignored when processing a list of revision IDs.",
+ "apihelp-query+deletedrevisions-param-end": "The timestamp to stop enumerating at. Ignored when processing a list of revision IDs.",
+ "apihelp-query+deletedrevisions-param-tag": "Only list revisions tagged with this tag.",
+ "apihelp-query+deletedrevisions-param-user": "Only list revisions by this user.",
+ "apihelp-query+deletedrevisions-param-excludeuser": "Don't list revisions by this user.",
+ "apihelp-query+deletedrevisions-param-limit": "The maximum amount of revisions to list.",
+ "apihelp-query+deletedrevisions-param-prop": "Which properties to get:\n;revid:Adds the revision ID of the deleted revision.\n;parentid:Adds the revision ID of the previous revision to the page.\n;user:Adds the user who made the revision.\n;userid:Adds the user ID who made the revision.\n;comment:Adds the comment of the revision.\n;parsedcomment:Adds the parsed comment of the revision.\n;minor:Tags if the revision is minor.\n;len:Adds the length (bytes) of the revision.\n;sha1:Adds the SHA-1 (base 16) of the revision.\n;content:Adds the content of the revision.\n;tags:Tags for the revision.",
+ "apihelp-query+deletedrevisions-example-titles": "List the deleted revisions of the pages <kbd>Main Page</kbd> and <kbd>Talk:Main Page</kbd>, with content.",
+ "apihelp-query+deletedrevisions-example-revids": "List the information for deleted revision <kbd>123456</kbd>.",
+
+ "apihelp-query+deletedrevs-description": "List deleted revisions.\n\nOperates in three modes:\n# List deleted revisions for the given titles, sorted by timestamp.\n# List deleted contributions for the given user, sorted by timestamp (no titles specified).\n# List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, $1user not set).\n\nCertain parameters only apply to some modes and are ignored in others.",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Mode|Modes}}: $2",
+ "apihelp-query+deletedrevs-param-start": "The timestamp to start enumerating from.",
+ "apihelp-query+deletedrevs-param-end": "The timestamp to stop enumerating at.",
+ "apihelp-query+deletedrevs-param-from": "Start listing at this title.",
+ "apihelp-query+deletedrevs-param-to": "Stop listing at this title.",
+ "apihelp-query+deletedrevs-param-prefix": "Search for all page titles that begin with this value.",
+ "apihelp-query+deletedrevs-param-unique": "List only one revision for each page.",
+ "apihelp-query+deletedrevs-param-tag": "Only list revisions tagged with this tag.",
+ "apihelp-query+deletedrevs-param-user": "Only list revisions by this user.",
+ "apihelp-query+deletedrevs-param-excludeuser": "Don't list revisions by this user.",
+ "apihelp-query+deletedrevs-param-namespace": "Only list pages in this namespace.",
+ "apihelp-query+deletedrevs-param-limit": "The maximum amount of revisions to list.",
+ "apihelp-query+deletedrevs-param-prop": "Which properties to get:\n;revid:Adds the revision ID of the deleted revision.\n;parentid:Adds the revision ID of the previous revision to the page.\n;user:Adds the user who made the revision.\n;userid:Adds the user ID whom made the revision.\n;comment:Adds the comment of the revision.\n;parsedcomment:Adds the parsed comment of the revision.\n;minor:Tags if the revision is minor.\n;len:Adds the length (bytes) of the revision.\n;sha1:Adds the SHA-1 (base 16) of the revision.\n;content:Adds the content of the revision.\n;token:<span class=\"apihelp-deprecated\">Deprecated.</span> Gives the edit token.\n;tags:Tags for the revision.",
+ "apihelp-query+deletedrevs-example-mode1": "List the last deleted revisions of the pages <kbd>Main Page</kbd> and <kbd>Talk:Main Page</kbd>, with content (mode 1).",
+ "apihelp-query+deletedrevs-example-mode2": "List the last 50 deleted contributions by <kbd>Bob</kbd> (mode 2).",
+ "apihelp-query+deletedrevs-example-mode3-main": "List the first 50 deleted revisions in the main namespace (mode 3).",
+ "apihelp-query+deletedrevs-example-mode3-talk": "List the first 50 deleted pages in the {{ns:talk}} namespace (mode 3).",
+
+ "apihelp-query+disabled-description": "This query module has been disabled.",
+
+ "apihelp-query+duplicatefiles-description": "List all files that are duplicates of the given files based on hash values.",
+ "apihelp-query+duplicatefiles-param-limit": "How many duplicate files to return.",
+ "apihelp-query+duplicatefiles-param-dir": "The direction in which to list.",
+ "apihelp-query+duplicatefiles-param-localonly": "Look only for files in the local repository.",
+ "apihelp-query+duplicatefiles-example-simple": "Look for duplicates of [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+duplicatefiles-example-generated": "Look for duplicates of all files.",
+
+ "apihelp-query+embeddedin-description": "Find all pages that embed (transclude) the given title.",
+ "apihelp-query+embeddedin-param-title": "Title to search. Cannot be used together with $1pageid.",
+ "apihelp-query+embeddedin-param-pageid": "Page ID to search. Cannot be used together with $1title.",
+ "apihelp-query+embeddedin-param-namespace": "The namespace to enumerate.",
+ "apihelp-query+embeddedin-param-dir": "The direction in which to list.",
+ "apihelp-query+embeddedin-param-filterredir": "How to filter for redirects.",
+ "apihelp-query+embeddedin-param-limit": "How many total pages to return.",
+ "apihelp-query+embeddedin-example-simple": "Show pages transcluding <kbd>Template:Stub</kbd>.",
+ "apihelp-query+embeddedin-example-generator": "Get information about pages transcluding <kbd>Template:Stub</kbd>.",
+
+ "apihelp-query+extlinks-description": "Returns all external URLs (not interwikis) from the given pages.",
+ "apihelp-query+extlinks-param-limit": "How many links to return.",
+ "apihelp-query+extlinks-param-protocol": "Protocol of the URL. If empty and <var>$1query</var> is set, the protocol is <kbd>http</kbd>. Leave both this and <var>$1query</var> empty to list all external links.",
+ "apihelp-query+extlinks-param-query": "Search string without protocol. Useful for checking whether a certain page contains a certain external url.",
+ "apihelp-query+extlinks-param-expandurl": "Expand protocol-relative URLs with the canonical protocol.",
+ "apihelp-query+extlinks-example-simple": "Get a list of external links on <kbd>Main Page<kbd>.",
+
+ "apihelp-query+exturlusage-description": "Enumerate pages that contain a given URL.",
+ "apihelp-query+exturlusage-param-prop": "Which pieces of information to include:\n;ids:Adds the ID of page.\n;title:Adds the title and namespace ID of the page.\n;url:Adds the URL used in the page.",
+ "apihelp-query+exturlusage-param-protocol": "Protocol of the URL. If empty and <var>$1query</var> set, the protocol is <kbd>http</kbd>. Leave both this and <var>$1query</var> empty to list all external links.",
+ "apihelp-query+exturlusage-param-query": "Search string without protocol. See [[Special:LinkSearch]]. Leave empty to list all external links.",
+ "apihelp-query+exturlusage-param-namespace": "The page namespaces to enumerate.",
+ "apihelp-query+exturlusage-param-limit": "How many pages to return.",
+ "apihelp-query+exturlusage-param-expandurl": "Expand protocol-relative URLs with the canonical protocol.",
+ "apihelp-query+exturlusage-example-simple": "Show pages linking to <kbd>http://www.mediawiki.org</kbd>.",
+
+ "apihelp-query+filearchive-description": "Enumerate all deleted files sequentially.",
+ "apihelp-query+filearchive-param-from": "The image title to start enumerating from.",
+ "apihelp-query+filearchive-param-to": "The image title to stop enumerating at.",
+ "apihelp-query+filearchive-param-prefix": "Search for all image titles that begin with this value.",
+ "apihelp-query+filearchive-param-limit": "How many images to return in total.",
+ "apihelp-query+filearchive-param-dir": "The direction in which to list.",
+ "apihelp-query+filearchive-param-sha1": "SHA1 hash of image. Overrides $1sha1base36.",
+ "apihelp-query+filearchive-param-sha1base36": "SHA1 hash of image in base 36 (used in MediaWiki).",
+ "apihelp-query+filearchive-param-prop": "Which image information to get:\n;sha1:Adds SHA-1 hash for the image.\n;timestamp:Adds timestamp for the uploaded version.\n;user:Adds user who uploaded the image version.\n;size:Adds the size of the image in bytes and the height, width and page count (if applicable).\n;dimensions:Alias for size.\n;description:Adds description the image version.\n;parseddescription:Parse the description on the version.\n;mime:Adds MIME of the image.\n;mediatype:Adds the media type of the image.\n;metadata:Lists Exif metadata for the version of the image.\n;bitdepth:Adds the bit depth of the version.\n;archivename:Adds the filename of the archive version for non-latest versions.",
+ "apihelp-query+filearchive-example-simple": "Show a list of all deleted files.",
+
+ "apihelp-query+filerepoinfo-description": "Return meta information about image repositories configured on the wiki.",
+ "apihelp-query+filerepoinfo-param-prop": "Which repository properties to get (there may be more available on some wikis):\n;apiurl:URL to the repository API - helpful for getting image info from the host.\n;name:The key of the repository - used in e.g. <var>[[mw:Manual:$wgForeignFileRepos|$wgForeignFileRepos]]</var> and [[Special:ApiHelp/query+imageinfo|imageinfo]] return values.\n;displayname:The human-readable name of the repository wiki.\n;rooturl:Root URL for image paths.\n;local:Whether that repository is the local one or not.",
+ "apihelp-query+filerepoinfo-example-simple": "Get information about file repositories.",
+
+ "apihelp-query+fileusage-description": "Find all pages that use the given files.",
+ "apihelp-query+fileusage-param-prop": "Which properties to get:\n;pageid:Page ID of each page.\n;title:Title of each page.\n;redirect:Flag if the page is a redirect.",
+ "apihelp-query+fileusage-param-namespace": "Only include pages in these namespaces.",
+ "apihelp-query+fileusage-param-limit": "How many to return.",
+ "apihelp-query+fileusage-param-show": "Show only items that meet these criteria:\n;redirect:Only show redirects.\n;!redirect:Only show non-redirects.",
+ "apihelp-query+fileusage-example-simple": "Get a list of pages using [[:File:Example.jpg]].",
+ "apihelp-query+fileusage-example-generator": "Get information about pages using [[:File:Example.jpg]].",
+
+ "apihelp-query+imageinfo-description": "Returns file information and upload history.",
+ "apihelp-query+imageinfo-param-prop": "Which file information to get:",
+ "apihelp-query+imageinfo-paramvalue-prop-timestamp": "Adds timestamp for the uploaded version.",
+ "apihelp-query+imageinfo-paramvalue-prop-user":"Adds the user who uploaded each file version.",
+ "apihelp-query+imageinfo-paramvalue-prop-userid":"Add the user ID that uploaded each file version.",
+ "apihelp-query+imageinfo-paramvalue-prop-comment":"Comment on the version.",
+ "apihelp-query+imageinfo-paramvalue-prop-parsedcomment":"Parse the comment on the version.",
+ "apihelp-query+imageinfo-paramvalue-prop-canonicaltitle":"Adds the canonical title of the file.",
+ "apihelp-query+imageinfo-paramvalue-prop-url":"Gives URL to the file and the description page.",
+ "apihelp-query+imageinfo-paramvalue-prop-size":"Adds the size of the file in bytes and the height, width and page count (if applicable).",
+ "apihelp-query+imageinfo-paramvalue-prop-dimensions":"Alias for size.",
+ "apihelp-query+imageinfo-paramvalue-prop-sha1":"Adds SHA-1 hash for the file.",
+ "apihelp-query+imageinfo-paramvalue-prop-mime":"Adds MIME type of the file.",
+ "apihelp-query+imageinfo-paramvalue-prop-thumbmime":"Adds MIME type of the image thumbnail (requires url and param $1urlwidth).",
+ "apihelp-query+imageinfo-paramvalue-prop-mediatype":"Adds the media type of the file.",
+ "apihelp-query+imageinfo-paramvalue-prop-metadata":"Lists Exif metadata for the version of the file.",
+ "apihelp-query+imageinfo-paramvalue-prop-commonmetadata":"Lists file format generic metadata for the version of the file.",
+ "apihelp-query+imageinfo-paramvalue-prop-extmetadata":"Lists formatted metadata combined from multiple sources. Results are HTML formatted.",
+ "apihelp-query+imageinfo-paramvalue-prop-archivename":"Adds the filename of the archive version for non-latest versions.",
+ "apihelp-query+imageinfo-paramvalue-prop-bitdepth":"Adds the bit depth of the version.",
+ "apihelp-query+imageinfo-paramvalue-prop-uploadwarning":"Used by the Special:Upload page to get information about an existing file. Not intended for use outside MediaWiki core.",
+ "apihelp-query+imageinfo-param-limit": "How many file revisions to return per file.",
+ "apihelp-query+imageinfo-param-start": "Timestamp to start listing from.",
+ "apihelp-query+imageinfo-param-end": "Timestamp to stop listing at.",
+ "apihelp-query+imageinfo-param-urlwidth": "If $2prop=url is set, a URL to an image scaled to this width will be returned.\nFor performance reasons if this option is used, no more than $1 scaled images will be returned.",
+ "apihelp-query+imageinfo-param-urlheight": "Similar to $1urlwidth.",
+ "apihelp-query+imageinfo-param-metadataversion": "Version of metadata to use. If <kbd>latest</kbd> is specified, use latest version. Defaults to <kbd>1</kbd> for backwards compatibility.",
+ "apihelp-query+imageinfo-param-extmetadatalanguage": "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.",
+ "apihelp-query+imageinfo-param-extmetadatamultilang": "If translations for extmetadata property are available, fetch all of them.",
+ "apihelp-query+imageinfo-param-extmetadatafilter": "If specified and non-empty, only these keys will be returned for $1prop=extmetadata.",
+ "apihelp-query+imageinfo-param-urlparam": "A handler specific parameter string. For example, PDFs might use <kbd>page15-100px</kbd>. <var>$1urlwidth</var> must be used and be consistent with <var>$1urlparam</var>.",
+ "apihelp-query+imageinfo-param-localonly": "Look only for files in the local repository.",
+ "apihelp-query+imageinfo-example-simple": "Fetch information about the current version of [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+imageinfo-example-dated": "Fetch information about versions of [[:File:Test.jpg]] from 2008 and later.",
+
+ "apihelp-query+images-description": "Returns all files contained on the given pages.",
+ "apihelp-query+images-param-limit": "How many files to return.",
+ "apihelp-query+images-param-images": "Only list these files. Useful for checking whether a certain page has a certain file.",
+ "apihelp-query+images-param-dir": "The direction in which to list.",
+ "apihelp-query+images-example-simple": "Get a list of files used in the [[Main Page]].",
+ "apihelp-query+images-example-generator": "Get information about all files used in the [[Main Page]].",
+
+ "apihelp-query+imageusage-description": "Find all pages that use the given image title.",
+ "apihelp-query+imageusage-param-title": "Title to search. Cannot be used together with $1pageid.",
+ "apihelp-query+imageusage-param-pageid": "Page ID to search. Cannot be used together with $1title.",
+ "apihelp-query+imageusage-param-namespace": "The namespace to enumerate.",
+ "apihelp-query+imageusage-param-dir": "The direction in which to list.",
+ "apihelp-query+imageusage-param-filterredir": "How to filter for redirects. If set to nonredirects when $1redirect is enabled, this is only applied to the second level.",
+ "apihelp-query+imageusage-param-limit": "How many total pages to return. If <var>$1redirect</var> is enabled, the limit applies to each level separately (which means up to 2 * <var>$1limit</var> results may be returned).",
+ "apihelp-query+imageusage-param-redirect": "If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.",
+ "apihelp-query+imageusage-example-simple": "Show pages using [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+imageusage-example-generator": "Get information about pages using [[:File:Albert Einstein Head.jpg]].",
+
+ "apihelp-query+info-description": "Get basic page information.",
+ "apihelp-query+info-param-prop": "Which additional properties to get:",
+ "apihelp-query+info-paramvalue-prop-protection": "List the protection level of each page.",
+ "apihelp-query+info-paramvalue-prop-talkid": "The page ID of the talk page for each non-talk page.",
+ "apihelp-query+info-paramvalue-prop-watched": "List the watched status of each page.",
+ "apihelp-query+info-paramvalue-prop-watchers": "The number of watchers, if allowed.",
+ "apihelp-query+info-paramvalue-prop-notificationtimestamp": "The watchlist notification timestamp of each page.",
+ "apihelp-query+info-paramvalue-prop-subjectid": "The page ID of the parent page for each talk page.",
+ "apihelp-query+info-paramvalue-prop-url": "Gives a full URL, an edit URL, and the canonical URL for each page.",
+ "apihelp-query+info-paramvalue-prop-readable": "Whether the user can read this page.",
+ "apihelp-query+info-paramvalue-prop-preload": "Gives the text returned by EditFormPreloadText.",
+ "apihelp-query+info-paramvalue-prop-displaytitle": "Gives the way the page title is actually displayed.",
+ "apihelp-query+info-param-testactions": "Test whether the current user can perform certain actions on the page.",
+ "apihelp-query+info-param-token": "Use [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] instead.",
+ "apihelp-query+info-example-simple": "Get information about the page <kbd>Main Page</kbd>.",
+ "apihelp-query+info-example-protection": "Get general and protection information about the page <kbd>Main Page</kbd>.",
+
+ "apihelp-query+iwbacklinks-description": "Find all pages that link to the given interwiki link.\n\nCan 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 interwiki links\".",
+ "apihelp-query+iwbacklinks-param-prefix": "Prefix for the interwiki.",
+ "apihelp-query+iwbacklinks-param-title": "Interwiki link to search for. Must be used with <var>$1blprefix</var>.",
+ "apihelp-query+iwbacklinks-param-limit": "How many total pages to return.",
+ "apihelp-query+iwbacklinks-param-prop": "Which properties to get:\n;iwprefix:Adds the prefix of the interwiki.\n;iwtitle:Adds the title of the interwiki.",
+ "apihelp-query+iwbacklinks-param-dir": "The direction in which to list.",
+ "apihelp-query+iwbacklinks-example-simple": "Get pages linking to [[wikibooks:Test]].",
+ "apihelp-query+iwbacklinks-example-generator": "Get information about pages linking to [[wikibooks:Test]].",
+
+ "apihelp-query+iwlinks-description": "Returns all interwiki links from the given pages.",
+ "apihelp-query+iwlinks-param-url": "Whether to get the full URL (cannot be used with $1prop).",
+ "apihelp-query+iwlinks-param-prop": "Which additional properties to get for each interlanguage link:\n;url:Adds the full URL.",
+ "apihelp-query+iwlinks-param-limit": "How many interwiki links to return.",
+ "apihelp-query+iwlinks-param-prefix": "Only return interwiki links with this prefix.",
+ "apihelp-query+iwlinks-param-title": "Interwiki link to search for. Must be used with <var>$1prefix</var>.",
+ "apihelp-query+iwlinks-param-dir": "The direction in which to list.",
+ "apihelp-query+iwlinks-example-simple": "Get interwiki links from the page <kbd>Main Page</kbd>.",
+
+ "apihelp-query+langbacklinks-description": "Find all pages that link to the given language link.\n\nCan be used to find all links with a language code, or all links to a title (with a given language). Using neither parameter is effectively \"all language links\".\n\nNote that this may not consider language links added by extensions.",
+ "apihelp-query+langbacklinks-param-lang": "Language for the language link.",
+ "apihelp-query+langbacklinks-param-title": "Language link to search for. Must be used with $1lang.",
+ "apihelp-query+langbacklinks-param-limit": "How many total pages to return.",
+ "apihelp-query+langbacklinks-param-prop": "Which properties to get:\n;lllang:Adds the language code of the language link.\n;lltitle:Adds the title of the language link.",
+ "apihelp-query+langbacklinks-param-dir": "The direction in which to list.",
+ "apihelp-query+langbacklinks-example-simple": "Get pages linking to [[:fr:Test]].",
+ "apihelp-query+langbacklinks-example-generator": "Get information about pages linking to [[:fr:Test]].",
+
+ "apihelp-query+langlinks-description": "Returns all interlanguage links from the given pages.",
+ "apihelp-query+langlinks-param-limit": "How many langlinks to return.",
+ "apihelp-query+langlinks-param-url": "Whether to get the full URL (cannot be used with <var>$1prop</var>).",
+ "apihelp-query+langlinks-param-prop": "Which additional properties to get for each interlanguage link:\n;url:Adds the full URL.\n;langname:Adds the localised language name (best effort). Use <var>$1inlanguagecode</var> to control the language.\n;autonym:Adds the native language name.",
+ "apihelp-query+langlinks-param-lang": "Only return language links with this language code.",
+ "apihelp-query+langlinks-param-title": "Link to search for. Must be used with <var>$1lang</var>.",
+ "apihelp-query+langlinks-param-dir": "The direction in which to list.",
+ "apihelp-query+langlinks-param-inlanguagecode": "Language code for localised language names.",
+ "apihelp-query+langlinks-example-simple": "Get interlanguage links from the page <kbd>Main Page</kbd>.",
+
+ "apihelp-query+links-description": "Returns all links from the given pages.",
+ "apihelp-query+links-param-namespace": "Show links in these namespaces only.",
+ "apihelp-query+links-param-limit": "How many links to return.",
+ "apihelp-query+links-param-titles": "Only list links to these titles. Useful for checking whether a certain page links to a certain title.",
+ "apihelp-query+links-param-dir": "The direction in which to list.",
+ "apihelp-query+links-example-simple": "Get links from the page <kbd>Main Page</kbd>",
+ "apihelp-query+links-example-generator": "Get information about the link pages in the page <kbd>Main Page</kbd>.",
+ "apihelp-query+links-example-namespaces": "Get links from the page <kbd>Main Page</kbd> in the {{ns:user}} and {{ns:template}} namespaces.",
+
+ "apihelp-query+linkshere-description": "Find all pages that link to the given pages.",
+ "apihelp-query+linkshere-param-prop": "Which properties to get:\n;pageid:Page ID of each page.\n;title:Title of each page.\n;redirect:Flag if the page is a redirect.",
+ "apihelp-query+linkshere-param-namespace": "Only include pages in these namespaces.",
+ "apihelp-query+linkshere-param-limit": "How many to return.",
+ "apihelp-query+linkshere-param-show": "Show only items that meet these criteria:\n;redirect:Only show redirects.\n;!redirect:Only show non-redirects.",
+ "apihelp-query+linkshere-example-simple": "Get a list of pages linking to the [[Main Page]].",
+ "apihelp-query+linkshere-example-generator": "Get information about pages linking to the [[Main Page]].",
+
+ "apihelp-query+logevents-description": "Get events from logs.",
+ "apihelp-query+logevents-param-prop": "Which properties to get:\n;ids:Adds the ID of the log event.\n;title:Adds the title of the page for the log event.\n;type:Adds the type of log event.\n;user:Adds the user responsible for the log event.\n;userid:Adds the user ID who was responsible for the log event.\n;timestamp:Adds the timestamp for the event.\n;comment:Adds the comment of the event.\n;parsedcomment:Adds the parsed comment of the event.\n;details:Lists additional details about the event.\n;tags:Lists tags for the event.",
+ "apihelp-query+logevents-param-type": "Filter log entries to only this type.",
+ "apihelp-query+logevents-param-action": "Filter log actions to only this action. Overrides <var>$1type</var>. Wildcard actions like <kbd>action/*</kbd> allows to specify any string for the asterisk.",
+ "apihelp-query+logevents-param-start": "The timestamp to start enumerating from.",
+ "apihelp-query+logevents-param-end": "The timestamp to end enumerating.",
+ "apihelp-query+logevents-param-user": "Filter entries to those made by the given user.",
+ "apihelp-query+logevents-param-title": "Filter entries to those related to a page.",
+ "apihelp-query+logevents-param-namespace": "Filter entries to those in the given namespace.",
+ "apihelp-query+logevents-param-prefix": "Filter entries that start with this prefix.",
+ "apihelp-query+logevents-param-tag": "Only list event entries tagged with this tag.",
+ "apihelp-query+logevents-param-limit": "How many total event entries to return.",
+ "apihelp-query+logevents-example-simple": "List recent log events.",
+
+ "apihelp-query+pagepropnames-description": "List all page property names in use on the wiki.",
+ "apihelp-query+pagepropnames-param-limit": "The maximum number of names to return.",
+ "apihelp-query+pagepropnames-example-simple": "Get first 10 property names.",
+
+ "apihelp-query+pageprops-description": "Get various properties defined in the page content.",
+ "apihelp-query+pageprops-param-prop": "Only list these props. Useful for checking whether a certain page uses a certain page prop.",
+ "apihelp-query+pageprops-example-simple": "Get properties for <kbd>Category:Foo</kbd>.",
+
+ "apihelp-query+pageswithprop-description": "List all pages using a given page property.",
+ "apihelp-query+pageswithprop-param-propname": "Page prop for which to enumerate pages.",
+ "apihelp-query+pageswithprop-param-prop": "Which pieces of information to include:\n;ids:Adds the page ID.\n;title:Adds the title and namespace ID of the page.\n;value:Adds the value of the page prop.",
+ "apihelp-query+pageswithprop-param-limit": "The maximum number of pages to return.",
+ "apihelp-query+pageswithprop-param-dir": "In which direction to sort.",
+ "apihelp-query+pageswithprop-example-simple": "List the first 10 pages using <code>&#123;&#123;DISPLAYTITLE:&#125;&#125;</code>.",
+ "apihelp-query+pageswithprop-example-generator": "Get page info about first 10 pages using <code>_&#95;NOTOC_&#95;</code>.",
+
+ "apihelp-query+prefixsearch-description": "Perform a prefix search for page titles.",
+ "apihelp-query+prefixsearch-param-search": "Search string.",
+ "apihelp-query+prefixsearch-param-namespace": "Namespaces to search.",
+ "apihelp-query+prefixsearch-param-limit": "Maximum number of results to return.",
+ "apihelp-query+prefixsearch-param-offset": "Number of results to skip.",
+ "apihelp-query+prefixsearch-example-simple": "Search for page titles beginning with <kbd>meaning</kbd>.",
+ "apihelp-query+protectedtitles-description": "List all titles protected from creation.",
+ "apihelp-query+protectedtitles-param-namespace": "Only list titles in these namespaces.",
+ "apihelp-query+protectedtitles-param-level": "Only list titles with these protection levels.",
+ "apihelp-query+protectedtitles-param-limit": "How many total pages to return.",
+ "apihelp-query+protectedtitles-param-start": "Start listing at this protection timestamp.",
+ "apihelp-query+protectedtitles-param-end": "Stop listing at this protection timestamp.",
+ "apihelp-query+protectedtitles-param-prop": "Which properties to get:\n;timestamp:Adds the timestamp of when protection was added.\n;user:Adds the user that added the protection.\n;userid:Adds the user ID that added the protection.\n;comment:Adds the comment for the protection.\n;parsedcomment:Adds the parsed comment for the protection.\n;expiry:Adds the timestamp of when the protection will be lifted.\n;level:Adds the protection level.",
+ "apihelp-query+protectedtitles-example-simple": "List protected titles.",
+ "apihelp-query+protectedtitles-example-generator": "Find links to protected titles in the main namespace.",
+
+ "apihelp-query+querypage-description": "Get a list provided by a QueryPage-based special page.",
+ "apihelp-query+querypage-param-page": "The name of the special page. Note, this is case sensitive.",
+ "apihelp-query+querypage-param-limit": "Number of results to return.",
+ "apihelp-query+querypage-example-ancientpages": "Return results from [[Special:Ancientpages]].",
+
+ "apihelp-query+random-description": "Get a set of random pages.\n\nPages are listed in a fixed sequence, only the starting point is random. This means that if, for example, <samp>Main Page</samp> is the first random page in the list, <samp>List of fictional monkeys</samp> will <em>always</em> be second, <samp>List of people on stamps of Vanuatu</samp> third, etc.\n\nIf the number of pages in the namespace is lower than <var>$1limit</var>, fewer pages will be returned. The same page will not be returned twice.",
+ "apihelp-query+random-param-namespace": "Return pages in these namespaces only.",
+ "apihelp-query+random-param-limit": "Limit how many random pages will be returned.",
+ "apihelp-query+random-param-redirect": "Load a random redirect instead of a random page.",
+ "apihelp-query+random-example-simple": "Return two random pages from the main namespace.",
+ "apihelp-query+random-example-generator": "Return page info about two random pages from the main namespace.",
+
+ "apihelp-query+recentchanges-description": "Enumerate recent changes.",
+ "apihelp-query+recentchanges-param-start": "The timestamp to start enumerating from.",
+ "apihelp-query+recentchanges-param-end": "The timestamp to end enumerating.",
+ "apihelp-query+recentchanges-param-namespace": "Filter changes to only these namespaces.",
+ "apihelp-query+recentchanges-param-user": "Only list changes by this user.",
+ "apihelp-query+recentchanges-param-excludeuser": "Don't list changes by this user.",
+ "apihelp-query+recentchanges-param-tag": "Only list changes tagged with this tag.",
+ "apihelp-query+recentchanges-param-prop": "Include additional pieces of information:\n;user:Adds the user responsible for the edit and tags if they are an IP.\n;userid:Adds the user ID responsible for the edit.\n;comment:Adds the comment for the edit.\n;parsedcomment:Adds the parsed comment for the edit.\n;flags:Adds flags for the edit.\n;timestamp:Adds timestamp of the edit.\n;title:Adds the page title of the edit.\n;ids:Adds the page ID, recent changes ID and the new and old revision ID.\n;sizes:Adds the new and old page length in bytes.\n;redirect:Tags edit if page is a redirect.\n;patrolled:Tags patrollable edits as being patrolled or unpatrolled.\n;loginfo:Adds log information (log ID, log type, etc) to log entries.\n;tags:Lists tags for the entry.\n;sha1:Adds the content checksum for entries associated with a revision.",
+ "apihelp-query+recentchanges-param-token": "Use <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> instead.",
+ "apihelp-query+recentchanges-param-show": "Show only items that meet these criteria. For example, to see only minor edits done by logged-in users, set $1show=minor|!anon.",
+ "apihelp-query+recentchanges-param-limit": "How many total changes to return.",
+ "apihelp-query+recentchanges-param-type": "Which types of changes to show.",
+ "apihelp-query+recentchanges-param-toponly": "Only list changes which are the latest revision.",
+ "apihelp-query+recentchanges-example-simple": "List recent changes.",
+ "apihelp-query+recentchanges-example-generator": "Get page info about recent unpatrolled changes.",
+
+ "apihelp-query+redirects-description": "Returns all redirects to the given pages.",
+ "apihelp-query+redirects-param-prop": "Which properties to get:\n;pageid:Page ID of each redirect.\n;title:Title of each redirect.\n;fragment:Fragment of each redirect, if any.",
+ "apihelp-query+redirects-param-namespace": "Only include pages in these namespaces.",
+ "apihelp-query+redirects-param-limit": "How many redirects to return.",
+ "apihelp-query+redirects-param-show": "Show only items that meet these criteria:\n;fragment:Only show redirects with a fragment.\n;!fragment:Only show redirects without a fragment.",
+ "apihelp-query+redirects-example-simple": "Get a list of redirects to the [[Main Page]].",
+ "apihelp-query+redirects-example-generator": "Get information about all redirects to the [[Main Page]].",
+
+ "apihelp-query+revisions-description": "Get revision information.\n\nMay be used in several ways:\n# Get data about a set of pages (last revision), by setting titles or pageids.\n# Get revisions for one given page, by using titles or pageids with start, end, or limit.\n# Get data about a set of revisions by setting their IDs with revids.",
+ "apihelp-query+revisions-paraminfo-singlepageonly": "May only be used with a single page (mode #2).",
+ "apihelp-query+revisions-param-startid": "From which revision ID to start enumeration.",
+ "apihelp-query+revisions-param-endid": "Stop revision enumeration on this revision ID.",
+ "apihelp-query+revisions-param-start": "From which revision timestamp to start enumeration.",
+ "apihelp-query+revisions-param-end": "Enumerate up to this timestamp.",
+ "apihelp-query+revisions-param-user": "Only include revisions made by user.",
+ "apihelp-query+revisions-param-excludeuser": "Exclude revisions made by user.",
+ "apihelp-query+revisions-param-tag": "Only list revisions tagged with this tag.",
+ "apihelp-query+revisions-param-token": "Which tokens to obtain for each revision.",
+ "apihelp-query+revisions-example-content": "Get data with content for the last revision of titles <kbd>API</kbd> and <kbd>Main Page</kbd>.",
+ "apihelp-query+revisions-example-last5": "Get last 5 revisions of the <kbd>Main Page</kbd>.",
+ "apihelp-query+revisions-example-first5": "Get first 5 revisions of the <kbd>Main Page</kbd>.",
+ "apihelp-query+revisions-example-first5-after": "Get first 5 revisions of the <kbd>Main Page</kbd> made after 2006-05-01.",
+ "apihelp-query+revisions-example-first5-not-localhost": "Get first 5 revisions of the <kbd>Main Page</kbd> that were not made by anonymous user <kbd>127.0.0.1</kbd>.",
+ "apihelp-query+revisions-example-first5-user": "Get first 5 revisions of the <kbd>Main Page</kbd> that were made by the user <kbd>MediaWiki default</kbd>.",
+ "apihelp-query+revisions+base-param-prop": "Which properties to get for each revision:\n;ids:The ID of the revision.\n;flags:Revision flags (minor).\n;timestamp:The timestamp of the revision.\n;user:User that made the revision.\n;userid:User ID of the revision creator.\n;size:Length (bytes) of the revision.\n;sha1:SHA-1 (base 16) of the revision.\n;contentmodel:Content model ID of the revision.\n;comment:Comment by the user for the revision.\n;parsedcomment:Parsed comment by the user for the revision.\n;content:Text of the revision.\n;tags:Tags for the revision.",
+ "apihelp-query+revisions+base-param-limit": "Limit how many revisions will be returned.",
+ "apihelp-query+revisions+base-param-expandtemplates": "Expand templates in revision content (requires $1prop=content).",
+ "apihelp-query+revisions+base-param-generatexml": "Generate XML parse tree for revision content (requires $1prop=content).",
+ "apihelp-query+revisions+base-param-parse": "Parse revision content (requires $1prop=content). For performance reasons, if this option is used, $1limit is enforced to 1.",
+ "apihelp-query+revisions+base-param-section": "Only retrieve the content of this section number.",
+ "apihelp-query+revisions+base-param-diffto": "Revision ID to diff each revision to. Use <kbd>prev</kbd>, <kbd>next</kbd> and <kbd>cur</kbd> for the previous, next and current revision respectively.",
+ "apihelp-query+revisions+base-param-difftotext": "Text to diff each revision to. Only diffs a limited number of revisions. Overrides <var>$1diffto</var>. If <var>$1section</var> is set, only that section will be diffed against this text",
+ "apihelp-query+revisions+base-param-contentformat": "Serialization format used for <var>$1difftotext</var> and expected for output of content.",
+
+ "apihelp-query+search-description": "Perform a full text search.",
+ "apihelp-query+search-param-search": "Search for all page titles (or content) that have this value.",
+ "apihelp-query+search-param-namespace": "Search only within these namespaces.",
+ "apihelp-query+search-param-what": "Which type of search to perform.",
+ "apihelp-query+search-param-info": "Which metadata to return.",
+ "apihelp-query+search-param-prop": "Which properties to return:\n;size:Adds the size of the page in bytes.\n;wordcount:Adds the word count of the page.\n;timestamp:Adds the timestamp of when the page was last edited.\n;snippet:Adds a parsed snippet of the page.\n;titlesnippet:Adds a parsed snippet of the page title.\n;redirectsnippet:Adds a parsed snippet of the redirect title.\n;redirecttitle:Adds the title of the matching redirect.\n;sectionsnippet:Adds a parsed snippet of the matching section title.\n;sectiontitle:Adds the title of the matching section.\n;score:<span class=\"apihelp-deprecated\">Deprecated and ignored.</span>\n;hasrelated:<span class=\"apihelp-deprecated\">Deprecated and ignored.</span>",
+ "apihelp-query+search-param-limit": "How many total pages to return.",
+ "apihelp-query+search-param-interwiki": "Include interwiki results in the search, if available.",
+ "apihelp-query+search-param-backend": "Which search backend to use, if not the default.",
+ "apihelp-query+search-example-simple": "Search for <kbd>meaning</kbd>.",
+ "apihelp-query+search-example-text": "Search texts for <kbd>meaning</kbd>.",
+ "apihelp-query+search-example-generator": "Ger page info about the pages returned for a search for <kbd>meaning</kbd>.",
+
+ "apihelp-query+siteinfo-description": "Return general information about the site.",
+ "apihelp-query+siteinfo-param-prop": "Which information to get:\n;general:Overall system information.\n;namespaces:List of registered namespaces and their canonical names.\n;namespacealiases:List of registered namespace aliases.\n;specialpagealiases:List of special page aliases.\n;magicwords:List of magic words and their aliases.\n;statistics:Returns site statistics.\n;interwikimap:Returns interwiki map (optionally filtered, optionally localised by using <var>$1inlanguagecode</var>).\n;dbrepllag:Returns database server with the highest replication lag.\n;usergroups:Returns user groups and the associated permissions.\n;libraries:Returns libraries installed on the wiki.\n;extensions:Returns extensions installed on the wiki.\n;fileextensions:Returns list of file extensions allowed to be uploaded.\n;rightsinfo:Returns wiki rights (license) information if available.\n;restrictions:Returns information on available restriction (protection) types.\n;languages:Returns a list of languages MediaWiki supports (optionally localised by using <var>$1inlanguagecode</var>).\n;skins:Returns a list of all enabled skins (optionally localised by using <var>$1inlanguagecode</var>, otherwise in the content language).\n;extensiontags:Returns a list of parser extension tags.\n;functionhooks:Returns a list of parser function hooks.\n;showhooks:Returns a list of all subscribed hooks (contents of <var>[[mw:Manual:$wgHooks|$wgHooks]]</var>).\n;variables:Returns a list of variable IDs.\n;protocols:Returns a list of protocols that are allowed in external links.\n;defaultoptions:Returns the default values for user preferences.",
+ "apihelp-query+siteinfo-param-filteriw": "Return only local or only nonlocal entries of the interwiki map.",
+ "apihelp-query+siteinfo-param-showalldb": "List all database servers, not just the one lagging the most.",
+ "apihelp-query+siteinfo-param-numberingroup": "Lists the number of users in user groups.",
+ "apihelp-query+siteinfo-param-inlanguagecode": "Language code for localised language names (best effort) and skin names.",
+ "apihelp-query+siteinfo-example-simple": "Fetch site information.",
+ "apihelp-query+siteinfo-example-interwiki": "Fetch a list of local interwiki prefixes.",
+ "apihelp-query+siteinfo-example-replag": "Check the current replication lag.",
+
+ "apihelp-query+stashimageinfo-description": "Returns file information for stashed files.",
+ "apihelp-query+stashimageinfo-param-filekey": "Key that identifies a previous upload that was stashed temporarily.",
+ "apihelp-query+stashimageinfo-param-sessionkey": "Alias for $1filekey, for backward compatibility.",
+ "apihelp-query+stashimageinfo-example-simple": "Returns information for a stashed file.",
+ "apihelp-query+stashimageinfo-example-params": "Returns thumbnails for two stashed files.",
+
+ "apihelp-query+tags-description": "List change tags.",
+ "apihelp-query+tags-param-limit": "The maximum number of tags to list.",
+ "apihelp-query+tags-param-prop": "Which properties to get:\n;name:Adds name of tag.\n;displayname:Adds system message for the tag.\n;description:Adds description of the tag.\n;hitcount:Adds the number of revisions and log entries that have this tag.\n;defined:Indicate whether the tag is defined.\n;source:Gets the sources of the tag, which may include <samp>extension</samp> for extension-defined tags and <samp>manual</samp> for tags that may be applied manually by users.\n;active:Whether the tag is still being applied.",
+ "apihelp-query+tags-example-simple": "List available tags.",
+
+ "apihelp-query+templates-description": "Returns all pages transcluded on the given pages.",
+ "apihelp-query+templates-param-namespace": "Show templates in this namespaces only.",
+ "apihelp-query+templates-param-limit": "How many templates to return.",
+ "apihelp-query+templates-param-templates": "Only list these templates. Useful for checking whether a certain page uses a certain template.",
+ "apihelp-query+templates-param-dir": "The direction in which to list.",
+ "apihelp-query+templates-example-simple": "Get the templates used on the page <kbd>Main Page</kbd>.",
+ "apihelp-query+templates-example-generator": "Get information about the template pages used on <kbd>Main Page</kbd>.",
+ "apihelp-query+templates-example-namespaces": "Get pages in the {{ns:user}} and {{ns:template}} namespaces that are transcluded on the page <kbd>Main Page</kbd>.",
+
+ "apihelp-query+tokens-description": "Gets tokens for data-modifying actions.",
+ "apihelp-query+tokens-param-type": "Types of token to request.",
+ "apihelp-query+tokens-example-simple": "Retrieve a csrf token (the default).",
+ "apihelp-query+tokens-example-types": "Retrieve a watch token and a patrol token.",
+
+ "apihelp-query+transcludedin-description": "Find all pages that transclude the given pages.",
+ "apihelp-query+transcludedin-param-prop": "Which properties to get:\n;pageid:Page ID of each page.\n;title:Title of each page.\n;redirect:Flag if the page is a redirect.",
+ "apihelp-query+transcludedin-param-namespace": "Only include pages in these namespaces.",
+ "apihelp-query+transcludedin-param-limit": "How many to return.",
+ "apihelp-query+transcludedin-param-show": "Show only items that meet these criteria:\n;redirect:Only show redirects.\n;!redirect:Only show non-redirects.",
+ "apihelp-query+transcludedin-example-simple": "Get a list of pages transcluding <kbd>Main Page</kbd>.",
+ "apihelp-query+transcludedin-example-generator": "Get information about pages transcluding <kbd>Main Page</kbd>.",
+
+ "apihelp-query+usercontribs-description": "Get all edits by a user.",
+ "apihelp-query+usercontribs-param-limit": "The maximum number of contributions to return.",
+ "apihelp-query+usercontribs-param-start": "The start timestamp to return from.",
+ "apihelp-query+usercontribs-param-end": "The end timestamp to return to.",
+ "apihelp-query+usercontribs-param-user": "The users to retrieve contributions for.",
+ "apihelp-query+usercontribs-param-userprefix": "Retrieve contributions for all users whose names begin with this value. Overrides $1user.",
+ "apihelp-query+usercontribs-param-namespace": "Only list contributions in these namespaces.",
+ "apihelp-query+usercontribs-param-prop": "Include additional pieces of information:\n;ids:Adds the page ID and revision ID.\n;title:Adds the title and namespace ID of the page.\n;timestamp:Adds the timestamp of the edit.\n;comment:Adds the comment of the edit.\n;parsedcomment:Adds the parsed comment of the edit.\n;size:Adds the new size of the edit.\n;sizediff:Adds the size delta of the edit against its parent.\n;flags:Adds flags of the edit.\n;patrolled:Tags patrolled edits.\n;tags:Lists tags for the edit.",
+ "apihelp-query+usercontribs-param-show": "Show only items that meet these criteria, e.g. non minor edits only: <kbd>$2show=!minor</kbd>.\n\nIf <kbd>$2show=patrolled</kbd> or <kbd>$2show=!patrolled</kbd> is set, revisions older than <var>[[mw:Manual:$wgRCMaxAge|$wgRCMaxAge]]</var> ($1 {{PLURAL:$1|second|seconds}}) won't be shown.",
+ "apihelp-query+usercontribs-param-tag": "Only list revisions tagged with this tag.",
+ "apihelp-query+usercontribs-param-toponly": "Only list changes which are the latest revision.",
+ "apihelp-query+usercontribs-example-user": "Show contributions of user <kbd>Example</kbd>.",
+ "apihelp-query+usercontribs-example-ipprefix": "Show contributions from all IP addresses with prefix <kbd>192.0.2.</kbd>.",
+
+ "apihelp-query+userinfo-description": "Get information about the current user.",
+ "apihelp-query+userinfo-param-prop": "Which pieces of information to include:\n;blockinfo:Tags if the current user is blocked, by whom, and for what reason.\n;hasmsg:Adds a tag <samp>message</samp> if the current user has pending messages.\n;groups:Lists all the groups the current user belongs to.\n;implicitgroups:Lists all the groups the current user is automatically a member of.\n;rights:Lists all the rights the current user has.\n;changeablegroups:Lists the groups the current user can add to and remove from.\n;options:Lists all preferences the current user has set.\n;preferencestoken:<span class=\"apihelp-deprecated\">Deprecated.</span> Get a token to change current user's preferences.\n;editcount:Adds the current user's edit count.\n;ratelimits:Lists all rate limits applying to the current user.\n;realname:Adds the user's real name.\n;email:Adds the user's email address and email authentication date.\n;acceptlang:Echoes the <code>Accept-Language</code> header sent by the client in a structured format.\n;registrationdate:Adds the user's registration date.\n;unreadcount:Adds the count of unread pages on the user's watchlist (maximum $1; returns <samp>$2</samp> if more).",
+ "apihelp-query+userinfo-example-simple": "Get information about the current user.",
+ "apihelp-query+userinfo-example-data": "Get additional information about the current user.",
+
+ "apihelp-query+users-description": "Get information about a list of users.",
+ "apihelp-query+users-param-prop": "Which pieces of information to include:\n;blockinfo:Tags if the user is blocked, by whom, and for what reason.\n;groups:Lists all the groups each user belongs to.\n;implicitgroups:Lists all the groups a user is automatically a member of.\n;rights:Lists all the rights each user has.\n;editcount:Adds the user's edit count.\n;registration:Adds the user's registration timestamp.\n;emailable:Tags if the user can and wants to receive email through [[Special:Emailuser]].\n;gender:Tags the gender of the user. Returns \"male\", \"female\", or \"unknown\".",
+ "apihelp-query+users-param-users": "A list of users to obtain information for.",
+ "apihelp-query+users-param-token": "Use <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> instead.",
+ "apihelp-query+users-example-simple": "Return information for user <kbd>Example</kbd>.",
+
+ "apihelp-query+watchlist-description": "Get recent changes to pages in the current user's watchlist.",
+ "apihelp-query+watchlist-param-allrev": "Include multiple revisions of the same page within given timeframe.",
+ "apihelp-query+watchlist-param-start": "The timestamp to start enumerating from.",
+ "apihelp-query+watchlist-param-end": "The timestamp to end enumerating.",
+ "apihelp-query+watchlist-param-namespace": "Filter changes to only the given namespaces.",
+ "apihelp-query+watchlist-param-user": "Only list changes by this user.",
+ "apihelp-query+watchlist-param-excludeuser": "Don't list changes by this user.",
+ "apihelp-query+watchlist-param-limit": "How many total results to return per request.",
+ "apihelp-query+watchlist-param-prop": "Which additional items to get:\n;ids:Adds revision IDs and page IDs.\n;title:Adds title of the page.\n;flags:Adds flags for the edit.\n;user:Adds the user who made the edit.\n;userid:Adds user ID of whom made the edit.\n;comment:Adds comment of the edit.\n;parsedcomment:Adds parsed comment of the edit.\n;timestamp:Adds timestamp of the edit.\n;patrol:Tags edits that are patrolled.\n;sizes:Adds the old and new lengths of the page.\n;notificationtimestamp:Adds timestamp of when the user was last notified about the edit.\n;loginfo:Adds log information where appropriate.",
+ "apihelp-query+watchlist-param-show": "Show only items that meet these criteria. For example, to see only minor edits done by logged-in users, set $1show=minor|!anon.",
+ "apihelp-query+watchlist-param-type": "Which types of changes to show:\n;edit:Regular page edits.\n;external:External changes.\n;new:Page creations.\n;log:Log entries.",
+ "apihelp-query+watchlist-param-owner": "Used along with $1token to access a different user's watchlist.",
+ "apihelp-query+watchlist-param-token": "A security token (available in the user's [[Special:Preferences#mw-prefsection-watchlist|preferences]]) to allow access to another user's watchlist.",
+ "apihelp-query+watchlist-example-simple": "List the top revision for recently changed pages on the current user's watchlist.",
+ "apihelp-query+watchlist-example-props": "Fetch additional information about the top revision for recently changed pages on the current user's watchlist.",
+ "apihelp-query+watchlist-example-allrev": "Fetch information about all recent changes to pages on the current user's watchlist.",
+ "apihelp-query+watchlist-example-generator": "Fetch page info for recently changed pages on the current user's watchlist.",
+ "apihelp-query+watchlist-example-generator-rev": "Fetch revision info for recent changes to pages on the current user's watchlist.",
+ "apihelp-query+watchlist-example-wlowner": "List the top revision for recently changed pages on the watchlist of user <kbd>Example</kbd>.",
+
+ "apihelp-query+watchlistraw-description": "Get all pages on the current user's watchlist.",
+ "apihelp-query+watchlistraw-param-namespace": "Only list pages in the given namespaces.",
+ "apihelp-query+watchlistraw-param-limit": "How many total results to return per request.",
+ "apihelp-query+watchlistraw-param-prop": "Which additional properties to get:\n;changed:Adds timestamp of when the user was last notified about the edit.",
+ "apihelp-query+watchlistraw-param-show": "Only list items that meet these criteria.",
+ "apihelp-query+watchlistraw-param-owner": "Used along with $1token to access a different user's watchlist.",
+ "apihelp-query+watchlistraw-param-token": "A security token (available in the user's [[Special:Preferences#mw-prefsection-watchlist|preferences]]) to allow access to another user's watchlist.",
+ "apihelp-query+watchlistraw-example-simple": "List pages on the current user's watchlist.",
+ "apihelp-query+watchlistraw-example-generator": "Fetch page info for pages on the current user's watchlist.",
+
+ "apihelp-revisiondelete-description": "Delete and undelete revisions.",
+ "apihelp-revisiondelete-param-type": "Type of revision deletion being performed.",
+ "apihelp-revisiondelete-param-target": "Page title for the revision deletion, if required for the type.",
+ "apihelp-revisiondelete-param-ids": "Identifiers for the revisions to be deleted.",
+ "apihelp-revisiondelete-param-hide": "What to hide for each revision.",
+ "apihelp-revisiondelete-param-show": "What to unhide for each revision.",
+ "apihelp-revisiondelete-param-suppress": "Whether to suppress data from administrators as well as others.",
+ "apihelp-revisiondelete-param-reason": "Reason for the deletion or undeletion.",
+ "apihelp-revisiondelete-example-revision": "Hide content for revision <kbd>12345</kbd> on the page <kbd>Main Page</kbd>.",
+ "apihelp-revisiondelete-example-log": "Hide all data on log entry <kbd>67890</kbd> with the reason <kbd>BLP violation</kbd>.",
+
+ "apihelp-rollback-description": "Undo the last edit to the page.\n\nIf the last user who edited the page made multiple edits in a row, they will all be rolled back.",
+ "apihelp-rollback-param-title": "Title of the page to roll back. Cannot be used together with <var>$1pageid</var>.",
+ "apihelp-rollback-param-pageid": "Page ID of the page to roll back. Cannot be used together with <var>$1title</var>.",
+ "apihelp-rollback-param-user": "Name of the user whose edits are to be rolled back.",
+ "apihelp-rollback-param-summary": "Custom edit summary. If empty, default summary will be used.",
+ "apihelp-rollback-param-markbot": "Mark the reverted edits and the revert as bot edits.",
+ "apihelp-rollback-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-rollback-example-simple": "Roll back the last edits to page <kbd>Main Page</kbd> by user <kbd>Example</kbd>.",
+ "apihelp-rollback-example-summary": "Roll back the last edits to page <kbd>Main Page</kbd> by IP user <kbd>192.0.2.5</kbd> with summary <kbd>Reverting vandalism</kbd>, and mark those edits and the revert as bot edits.",
+
+ "apihelp-rsd-description": "Export an RSD (Really Simple Discovery) schema.",
+ "apihelp-rsd-example-simple": "Export the RSD schema.",
+
+ "apihelp-setnotificationtimestamp-description": "Update the notification timestamp for watched pages.\n\nThis affects the highlighting of changed pages in the watchlist and history, and the sending of email when the \"Email me when a page on my watchlist is changed\" preference is enabled.",
+ "apihelp-setnotificationtimestamp-param-entirewatchlist": "Work on all watched pages.",
+ "apihelp-setnotificationtimestamp-param-timestamp": "Timestamp to which to set the notification timestamp.",
+ "apihelp-setnotificationtimestamp-param-torevid": "Revision to set the notification timestamp to (one page only).",
+ "apihelp-setnotificationtimestamp-param-newerthanrevid": "Revision to set the notification timestamp newer than (one page only).",
+ "apihelp-setnotificationtimestamp-example-all": "Reset the notification status for the entire watchlist.",
+ "apihelp-setnotificationtimestamp-example-page": "Reset the notification status for <kbd>Main page</kbd>.",
+ "apihelp-setnotificationtimestamp-example-pagetimestamp": "Set the notification timestamp for <kbd>Main page</kbd> so all edits since 1 January 2012 are unviewed.",
+ "apihelp-setnotificationtimestamp-example-allpages": "Reset the notification status for pages in the <kbd>{{ns:user}}</kbd> namespace.",
+
+ "apihelp-tag-description": "Add or remove change tags from individual revisions or log entries.",
+ "apihelp-tag-param-rcid": "One or more recent changes IDs from which to add or remove the tag.",
+ "apihelp-tag-param-revid": "One or more revision IDs from which to add or remove the tag.",
+ "apihelp-tag-param-logid": "One or more log entry IDs from which to add or remove the tag.",
+ "apihelp-tag-param-add": "Tags to add. Only manually defined tags can be added.",
+ "apihelp-tag-param-remove": "Tags to remove. Only tags that are either manually defined or completely undefined can be removed.",
+ "apihelp-tag-param-reason": "Reason for the change.",
+ "apihelp-tag-example-rev": "Add the <kbd>vandalism</kbd> tag from revision ID 123 without specifying a reason",
+ "apihelp-tag-example-log": "Remove the <kbd>spam</kbd> tag from log entry ID 123 with the reason <kbd>Wrongly applied</kbd>",
+
+ "apihelp-tokens-description": "Get tokens for data-modifying actions.\n\nThis module is deprecated in favor of [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].",
+ "apihelp-tokens-param-type": "Types of token to request.",
+ "apihelp-tokens-example-edit": "Retrieve an edit token (the default).",
+ "apihelp-tokens-example-emailmove": "Retrieve an email token and a move token.",
+
+ "apihelp-unblock-description": "Unblock a user.",
+ "apihelp-unblock-param-id": "ID of the block to unblock (obtained through <kbd>list=blocks</kbd>). Cannot be used together with <var>$1user</var>.",
+ "apihelp-unblock-param-user": "Username, IP address or IP range to unblock. Cannot be used together with <var>$1id</var>.",
+ "apihelp-unblock-param-reason": "Reason for unblock.",
+ "apihelp-unblock-example-id": "Unblock block ID #<kbd>105</kbd>.",
+ "apihelp-unblock-example-user": "Unblock user <kbd>Bob</kbd> with reason <kbd>Sorry Bob</kbd>.",
+
+ "apihelp-undelete-description": "Restore revisions of a deleted page.\n\nA list of deleted revisions (including timestamps) can be retrieved through [[Special:ApiHelp/query+deletedrevs|list=deletedrevs]], and a list of deleted file IDs can be retrieved through [[Special:ApiHelp/query+filearchive|list=filearchive]].",
+ "apihelp-undelete-param-title": "Title of the page to restore.",
+ "apihelp-undelete-param-reason": "Reason for restoring.",
+ "apihelp-undelete-param-timestamps": "Timestamps of the revisions to restore. If both <var>$1timestamps</var> and <var>$1fileids</var> are empty, all will be restored.",
+ "apihelp-undelete-param-fileids": "IDs of the file revisions to restore. If both <var>$1timestamps</var> and <var>$1fileids</var> are empty, all will be restored.",
+ "apihelp-undelete-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-undelete-example-page": "Undelete page <kbd>Main Page</kbd>.",
+ "apihelp-undelete-example-revisions": "Undelete two revisions of page <kbd>Main Page</kbd>.",
+
+ "apihelp-upload-description": "Upload a file, or get the status of pending uploads.\n\nSeveral methods are available:\n* Upload file contents directly, using the <var>$1file</var> parameter.\n* Upload the file in pieces, using the <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var> parameters.* Have the MediaWiki server fetch a file from a URL, using the <var>$1url</var> parameter.\n* Complete an earlier upload that failed due to warnings, using the <var>$1filekey</var> parameter.\nNote that the HTTP POST must be done as a file upload (i.e. using <code>multipart/form-data</code>) when sending the <var>$1file</var>.",
+ "apihelp-upload-param-filename": "Target filename.",
+ "apihelp-upload-param-comment": "Upload comment. Also used as the initial page text for new files if <var>$1text</var> is not specified.",
+ "apihelp-upload-param-text": "Initial page text for new files.",
+ "apihelp-upload-param-watch": "Watch the page.",
+ "apihelp-upload-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
+ "apihelp-upload-param-ignorewarnings": "Ignore any warnings.",
+ "apihelp-upload-param-file": "File contents.",
+ "apihelp-upload-param-url": "URL to fetch the file from.",
+ "apihelp-upload-param-filekey": "Key that identifies a previous upload that was stashed temporarily.",
+ "apihelp-upload-param-sessionkey": "Same as $1filekey, maintained for backward compatibility.",
+ "apihelp-upload-param-stash": "If set, the server will stash the file temporarily instead of adding it to the repository.",
+ "apihelp-upload-param-filesize": "Filesize of entire upload.",
+ "apihelp-upload-param-offset": "Offset of chunk in bytes.",
+ "apihelp-upload-param-chunk": "Chunk contents.",
+ "apihelp-upload-param-async": "Make potentially large file operations asynchronous when possible.",
+ "apihelp-upload-param-asyncdownload": "Make fetching a URL asynchronous.",
+ "apihelp-upload-param-leavemessage": "If asyncdownload is used, leave a message on the user talk page if finished.",
+ "apihelp-upload-param-statuskey": "Fetch the upload status for this file key (upload by URL).",
+ "apihelp-upload-param-checkstatus": "Only fetch the upload status for the given file key.",
+ "apihelp-upload-example-url": "Upload from a URL.",
+ "apihelp-upload-example-filekey": "Complete an upload that failed due to warnings.",
+
+ "apihelp-userrights-description": "Change a user's group membership.",
+ "apihelp-userrights-param-user": "User name.",
+ "apihelp-userrights-param-userid": "User ID.",
+ "apihelp-userrights-param-add": "Add the user to these groups.",
+ "apihelp-userrights-param-remove": "Remove the user from these groups.",
+ "apihelp-userrights-param-reason": "Reason for the change.",
+ "apihelp-userrights-example-user": "Add user <kbd>FooBot</kbd> to group <kbd>bot</kbd>, and remove from groups <kbd>sysop</kbd> and <kbd>bureaucrat</kbd>.",
+ "apihelp-userrights-example-userid": "Add the user with ID <kbd>123</kbd> to group <kbd>bot</kbd>, and remove from groups <kbd>sysop</kbd> and <kbd>bureaucrat</kbd>.",
+
+ "apihelp-watch-description": "Add or remove pages from the current user's watchlist.",
+ "apihelp-watch-param-title": "The page to (un)watch. Use <var>$1titles</var> instead.",
+ "apihelp-watch-param-unwatch": "If set the page will be unwatched rather than watched.",
+ "apihelp-watch-example-watch": "Watch the page <kbd>Main Page</kbd>.",
+ "apihelp-watch-example-unwatch": "Unwatch the page <kbd>Main Page</kbd>.",
+ "apihelp-watch-example-generator": "Watch the first few pages in the main namespace.",
+
+ "apihelp-format-example-generic": "Format the query result in the $1 format.",
+ "apihelp-dbg-description": "Output data in PHP's <code>var_export()</code> format.",
+ "apihelp-dbgfm-description": "Output data in PHP's <code>var_export()</code> format (pretty-print in HTML).",
+ "apihelp-dump-description": "Output data in PHP's <code>var_dump()</code> format.",
+ "apihelp-dumpfm-description": "Output data in PHP's <code>var_dump()</code> format (pretty-print in HTML).",
+ "apihelp-json-description": "Output data in JSON format.",
+ "apihelp-json-param-callback": "If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.",
+ "apihelp-json-param-utf8": "If specified, encodes most (but not all) non-ASCII characters as UTF-8 instead of replacing them with hexadecimal escape sequences. Default when <var>formatversion</var> is not <kbd>1</kbd>.",
+ "apihelp-json-param-ascii": "If specified, encodes all non-ASCII using hexadecimal escape sequences. Default when <var>formatversion</var> is <kbd>1</kbd>.",
+ "apihelp-json-param-formatversion": "Output formatting:\n;1:Backwards-compatible format (XML-style booleans, <samp>*</samp> keys for content nodes, etc.).\n;2:Experimental modern format. Details may change!\n;latest:Use the latest format (currently <kbd>2</kbd>), may change without warning.",
+ "apihelp-jsonfm-description": "Output data in JSON format (pretty-print in HTML).",
+ "apihelp-none-description": "Output nothing.",
+ "apihelp-php-description": "Output data in serialized PHP format.",
+ "apihelp-php-param-formatversion": "Output formatting:\n;1:Backwards-compatible format (XML-style booleans, <samp>*</samp> keys for content nodes, etc.).\n;2:Experimental modern format. Details may change!\n;latest:Use the latest format (currently <kbd>2</kbd>), may change without warning.",
+ "apihelp-phpfm-description": "Output data in serialized PHP format (pretty-print in HTML).",
+ "apihelp-rawfm-description": "Output data with the debugging elements in JSON format (pretty-print in HTML).",
+ "apihelp-txt-description": "Output data in PHP's <code>print_r()</code> format.",
+ "apihelp-txtfm-description": "Output data in PHP's <code>print_r()</code> format (pretty-print in HTML).",
+ "apihelp-wddx-description": "Output data in WDDX format.",
+ "apihelp-wddxfm-description": "Output data in WDDX format (pretty-print in HTML).",
+ "apihelp-xml-description": "Output data in XML format.",
+ "apihelp-xml-param-xslt": "If specified, adds the named page as an XSL stylesheet. The value must be a title in the {{ns:mediawiki}} namespace ending in <code>.xsl</code>.",
+ "apihelp-xml-param-includexmlnamespace": "If specified, adds an XML namespace.",
+ "apihelp-xmlfm-description": "Output data in XML format (pretty-print in HTML).",
+ "apihelp-yaml-description": "Output data in YAML format.",
+ "apihelp-yamlfm-description": "Output data in YAML format (pretty-print in HTML).",
+
+ "api-format-title": "MediaWiki API result",
+ "api-format-prettyprint-header": "This is the HTML representation of the $1 format. HTML is good for debugging, but is unsuitable for application use.\n\nSpecify the <var>format</var> parameter to change the output format. To see the non-HTML representation of the $1 format, set <kbd>format=$2</kbd>.\n\nSee the [[mw:API|complete documentation]], or the [[Special:ApiHelp/main|API help]] for more information.",
+
+ "api-orm-param-props": "Fields to query.",
+ "api-orm-param-limit": "Max amount of rows to return.",
+
+ "api-pageset-param-titles": "A list of titles to work on.",
+ "api-pageset-param-pageids": "A list of page IDs to work on.",
+ "api-pageset-param-revids": "A list of revision IDs to work on.",
+ "api-pageset-param-generator": "Get the list of pages to work on by executing the specified query module.\n\n<strong>Note:</strong> Generator parameter names must be prefixed with a \"g\", see examples.",
+ "api-pageset-param-redirects-generator": "Automatically resolve redirects in <var>$1titles</var>, <var>$1pageids</var>, and <var>$1revids</var>, and in pages returned by <var>$1generator</var>.",
+ "api-pageset-param-redirects-nogenerator": "Automatically resolve redirects in <var>$1titles</var>, <var>$1pageids</var>, and <var>$1revids</var>.",
+ "api-pageset-param-converttitles": "Convert titles to other variants if necessary. Only works if the wiki's content language supports variant conversion. Languages that support variant conversion include $1.",
+
+ "api-help-title": "MediaWiki API help",
+ "api-help-lead": "This is an auto-generated MediaWiki API documentation page.\n\nDocumentation and examples: https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "Main module",
+ "api-help-fallback-description": "$1",
+ "api-help-fallback-parameter": "$1",
+ "api-help-fallback-example": "$1",
+ "api-help-flags": "",
+ "api-help-flag-deprecated": "This module is deprecated.",
+ "api-help-flag-internal": "<strong>This module is internal or unstable.</strong> Its operation may change without notice.",
+ "api-help-flag-readrights": "This module requires read rights.",
+ "api-help-flag-writerights": "This module requires write rights.",
+ "api-help-flag-mustbeposted": "This module only accepts POST requests.",
+ "api-help-flag-generator": "This module can be used as a generator.",
+ "api-help-help-urls": "",
+ "api-help-parameters": "{{PLURAL:$1|Parameter|Parameters}}:",
+ "api-help-param-deprecated": "Deprecated.",
+ "api-help-param-required": "This parameter is required.",
+ "api-help-param-list": "{{PLURAL:$1|1=One value|2=Values (separate with <kbd>{{!}}</kbd>)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Must be empty|Can be empty, or $2}}",
+ "api-help-param-limit": "No more than $1 allowed.",
+ "api-help-param-limit2": "No more than $1 ($2 for bots) allowed.",
+ "api-help-param-integer-min": "The {{PLURAL:$1|1=value|2=values}} must be no less than $2.",
+ "api-help-param-integer-max": "The {{PLURAL:$1|1=value|2=values}} must be no greater than $3.",
+ "api-help-param-integer-minmax": "The {{PLURAL:$1|1=value|2=values}} must be between $2 and $3.",
+ "api-help-param-upload": "Must be posted as a file upload using multipart/form-data.",
+ "api-help-param-multi-separate": "Separate values with <kbd>|</kbd>.",
+ "api-help-param-multi-max": "Maximum number of values is {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} for bots).",
+ "api-help-param-default": "Default: $1",
+ "api-help-param-default-empty": "Default: <span class=\"apihelp-empty\">(empty)</span>",
+ "api-help-param-token": "A \"$1\" token retrieved from [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+ "api-help-param-token-webui": "For compatibility, the token used in the web UI is also accepted.",
+ "api-help-param-disabled-in-miser-mode": "Disabled due to [[mw:Manual:$wgMiserMode|miser mode]].",
+ "api-help-param-limited-in-miser-mode": "<strong>Note:</strong> Due to [[mw:Manual:$wgMiserMode|miser mode]], using this may result in fewer than <var>$1limit</var> results returned before continuing; in extreme cases, zero results may be returned.",
+ "api-help-param-direction": "In which direction to enumerate:\n;newer:List oldest first. Note: $1start has to be before $1end.\n;older:List newest first (default). Note: $1start has to be later than $1end.",
+ "api-help-param-continue": "When more results are available, use this to continue.",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(no description)</span>",
+ "api-help-examples": "{{PLURAL:$1|Example|Examples}}:",
+ "api-help-permissions": "{{PLURAL:$1|Permission|Permissions}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Granted to}}: $2",
+ "api-help-right-apihighlimits": "Use higher limits in API queries (slow queries: $1; fast queries: $2). The limits for slow queries also apply to multivalue parameters.",
+
+ "api-credits-header": "Credits",
+ "api-credits": "API developers:\n* Roan Kattouw (lead developer Sep 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (creator, lead developer Sep 2006–Sep 2007)\n* Brad Jorsch (lead developer 2013–present)\n\nPlease send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org\nor file a bug report at https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/es.json b/includes/api/i18n/es.json
new file mode 100644
index 00000000..d0974772
--- /dev/null
+++ b/includes/api/i18n/es.json
@@ -0,0 +1,206 @@
+{
+ "@metadata": {
+ "authors": [
+ "Macofe",
+ "Effy",
+ "Alan",
+ "Fitoschido",
+ "JasterTDC",
+ "Edslov"
+ ]
+ },
+ "apihelp-main-param-action": "Qué acción se realizará.",
+ "apihelp-main-param-format": "El formato de la salida.",
+ "apihelp-main-param-curtimestamp": "Incluir la marca de tiempo actual en el resultado.",
+ "apihelp-block-description": "Bloquear usuario",
+ "apihelp-block-param-user": "El nombre de usuario, dirección IP o intervalo de IP que quieres bloquear.",
+ "apihelp-block-param-reason": "Razón para el bloqueo.",
+ "apihelp-block-param-anononly": "Bloquear solo usuarios anónimos (es decir, desactivar ediciones anónimas de esta dirección IP).",
+ "apihelp-block-param-nocreate": "Prevenir la creación de cuentas.",
+ "apihelp-block-param-reblock": "Si la cuenta ya está bloqueada, sobrescribir el bloqueo existente.",
+ "apihelp-block-param-watchuser": "Vigilar las páginas de usuario y de discusión del usuario o de la dirección IP.",
+ "apihelp-compare-param-fromtitle": "Primer título para comparar",
+ "apihelp-createaccount-description": "Crear una nueva cuenta de usuario.",
+ "apihelp-createaccount-param-name": "Nombre de usuario.",
+ "apihelp-createaccount-param-email": "Dirección de correo electrónico del usuario (opcional).",
+ "apihelp-createaccount-param-realname": "Nombre verdadero del usuario (opcional).",
+ "apihelp-createaccount-example-pass": "Crear usuario <kbd>testuser</kbd> con la contraseña <kbd>test123</kbd>.",
+ "apihelp-delete-description": "Borrar una página.",
+ "apihelp-delete-param-watch": "Añadir esta página a la lista de seguimiento del usuario actual.",
+ "apihelp-delete-param-unwatch": "Quitar la página de la lista de seguimiento del usuario actual.",
+ "apihelp-delete-example-simple": "Borrar la <kbd>Página principal</kbd>",
+ "apihelp-disabled-description": "Se desactivó este módulo.",
+ "apihelp-edit-description": "Crear y editar páginas.",
+ "apihelp-edit-param-sectiontitle": "El título de una sección nueva.",
+ "apihelp-edit-param-text": "Contenido de la página.",
+ "apihelp-edit-param-minor": "Edición menor.",
+ "apihelp-edit-param-notminor": "Edición no menor.",
+ "apihelp-edit-param-bot": "Marcar esta edición como de bot.",
+ "apihelp-edit-param-createonly": "No editar la página si ya existe.",
+ "apihelp-edit-param-nocreate": "Producir un error si la página no existe.",
+ "apihelp-edit-param-watch": "Añadir la página a la lista de seguimiento del usuario actual.",
+ "apihelp-edit-param-unwatch": "Quitar la página de la lista de seguimiento del usuario actual.",
+ "apihelp-edit-example-edit": "Editar una página",
+ "apihelp-edit-example-prepend": "Anteponer <kbd>_&#95;NOTOC_&#95;</kbd> a una página.",
+ "apihelp-edit-example-undo": "Deshacer intervalo de revisiones 13579-13585 con resumen automático",
+ "apihelp-emailuser-description": "Enviar un mensaje de correo electrónico a un usuario.",
+ "apihelp-emailuser-param-target": "Cuenta de usuario destinatario.",
+ "apihelp-emailuser-param-subject": "Encabezamiento de asunto.",
+ "apihelp-emailuser-param-text": "Cuerpo del mensaje.",
+ "apihelp-emailuser-param-ccme": "Enviarme una copia de este mensaje.",
+ "apihelp-expandtemplates-param-title": "Título de la página.",
+ "apihelp-expandtemplates-param-text": "Sintaxis wiki que se convertirá.",
+ "apihelp-feedcontributions-description": "Devuelve el canal de contribuciones de un usuario.",
+ "apihelp-feedcontributions-param-feedformat": "El formato del canal.",
+ "apihelp-feedcontributions-param-year": "A partir del año (y anteriores).",
+ "apihelp-feedcontributions-param-month": "A partir del mes (y anteriores).",
+ "apihelp-feedcontributions-param-tagfilter": "Filtrar las contribuciones que tienen estas etiquetas.",
+ "apihelp-feedcontributions-param-deletedonly": "Mostrar solo las contribuciones borradas.",
+ "apihelp-feedcontributions-param-toponly": "Mostrar solo ediciones que son últimas revisiones.",
+ "apihelp-feedcontributions-param-newonly": "Mostrar solo ediciones que son creaciones de páginas.",
+ "apihelp-feedcontributions-param-showsizediff": "Mostrar la diferencia de tamaño entre revisiones.",
+ "apihelp-feedcontributions-example-simple": "Devolver las contribuciones del usuario <kbd>Ejemplo</kbd>.",
+ "apihelp-feedrecentchanges-description": "Devuelve un canal de cambios recientes.",
+ "apihelp-feedrecentchanges-param-feedformat": "El formato del canal.",
+ "apihelp-feedrecentchanges-param-invert": "Todos los espacios de nombres menos el que está seleccionado.",
+ "apihelp-feedrecentchanges-param-associated": "Incluir el espacio de nombres asociado (discusión o principal).",
+ "apihelp-feedrecentchanges-param-limit": "Número máximo de resultados que devolver.",
+ "apihelp-feedrecentchanges-param-from": "Mostrar los cambios realizados a partir de entonces.",
+ "apihelp-feedrecentchanges-param-hideminor": "Ocultar cambios menores.",
+ "apihelp-feedrecentchanges-param-hidebots": "Ocultar los cambios realizados por bots.",
+ "apihelp-feedrecentchanges-param-hideanons": "Ocultar los cambios realizados por usuarios anónimos.",
+ "apihelp-feedrecentchanges-param-hideliu": "Ocultar los cambios realizados por usuarios registrados.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Ocultar los cambios patrullados.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Ocultar los cambios realizados por el usuario actual.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filtrar por etiquetas.",
+ "apihelp-feedrecentchanges-param-target": "Mostrar solo los cambios en las páginas enlazadas en esta.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Mostrar los cambios en páginas enlazadas con la página seleccionada.",
+ "apihelp-feedrecentchanges-example-simple": "Mostrar los cambios recientes",
+ "apihelp-feedrecentchanges-example-30days": "Mostrar los cambios recientes limitados a 30 días",
+ "apihelp-feedwatchlist-description": "Devuelve el canal de una lista de seguimiento.",
+ "apihelp-feedwatchlist-param-feedformat": "El formato del canal.",
+ "apihelp-feedwatchlist-param-linktosections": "Enlazar directamente a las secciones cambiadas de ser posible.",
+ "apihelp-feedwatchlist-example-default": "Mostrar el canal de la lista de seguimiento.",
+ "apihelp-feedwatchlist-example-all6hrs": "Mostrar todos los cambios en páginas vigiladas en las últimas 6 horas.",
+ "apihelp-filerevert-description": "Revertir el archivo a una versión anterior.",
+ "apihelp-filerevert-param-filename": "Nombre de archivo final, sin el prefijo Archivo:",
+ "apihelp-filerevert-param-comment": "Comentario de carga.",
+ "apihelp-help-description": "Mostrar la ayuda para los módulos especificados.",
+ "apihelp-help-example-main": "Ayuda del módulo principal",
+ "apihelp-help-example-recursive": "Toda la ayuda en una página",
+ "apihelp-help-example-help": "Ayuda del módulo de ayuda en sí",
+ "apihelp-imagerotate-description": "Girar una o más imágenes.",
+ "apihelp-imagerotate-param-rotation": "Grados que rotar una imagen en sentido horario.",
+ "apihelp-imagerotate-example-simple": "Rotar <kbd>File:Ejemplo.png</kbd> <kbd>90</kbd> grados.",
+ "apihelp-imagerotate-example-generator": "Rotar todas las imágenes en la <kbd>Categoría:Girar</kbd> <kbd>180</kbd> grados.",
+ "apihelp-import-param-summary": "Resumen de importación.",
+ "apihelp-import-param-xml": "Se cargó el archivo XML.",
+ "apihelp-import-param-rootpage": "Importar como subpágina de esta página.",
+ "apihelp-login-param-name": "Nombre de usuario.",
+ "apihelp-login-param-password": "Contraseña.",
+ "apihelp-login-param-domain": "Dominio (opcional).",
+ "apihelp-login-example-login": "Acceder",
+ "apihelp-logout-description": "Salir y vaciar los datos de la sesión.",
+ "apihelp-logout-example-logout": "Cerrar la sesión del usuario actual",
+ "apihelp-managetags-param-reason": "Un motivo opcional para crear, eliminar, activar o desactivar la etiqueta.",
+ "apihelp-managetags-example-delete": "Eliminar la etiqueta <kbd>vandlaismo</kbd> con el motivo <kbd>mal deletreado</kbd>",
+ "apihelp-move-description": "Mover una página.",
+ "apihelp-move-param-reason": "Motivo del cambio de nombre.",
+ "apihelp-move-param-movetalk": "Renombrar la página de discusión si existe.",
+ "apihelp-move-param-movesubpages": "Renombrar las subpáginas si procede.",
+ "apihelp-move-param-noredirect": "No crear una redirección.",
+ "apihelp-move-param-watch": "Añadir la página y su redirección a la lista de seguimiento del usuario actual.",
+ "apihelp-move-param-unwatch": "Eliminar la página y la redirección de la lista de seguimiento del usuario.",
+ "apihelp-move-param-ignorewarnings": "Ignorar cualquier aviso.",
+ "apihelp-opensearch-description": "Buscar en el wiki mediante el protocolo OpenSearch.",
+ "apihelp-opensearch-param-search": "Buscar cadena.",
+ "apihelp-options-example-reset": "Restablecer todas las preferencias",
+ "apihelp-paraminfo-description": "Obtener información acerca de los módulos de la API.",
+ "apihelp-paraminfo-param-helpformat": "Formato de las cadenas de ayuda.",
+ "apihelp-patrol-example-rcid": "Patrullar un cambio reciente",
+ "apihelp-patrol-example-revid": "Patrullar una revisión",
+ "apihelp-protect-param-reason": "Motivo de la (des)protección.",
+ "apihelp-protect-example-protect": "Proteger una página",
+ "apihelp-query+allcategories-description": "Enumerar todas las categorías.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Solo puede usarse con <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "No puede ser utilizado con <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-param-from": "Empezar a listar en este título.",
+ "apihelp-query+alldeletedrevisions-param-to": "Terminar de listar en este título.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Buscar todos los títulos de las páginas que comiencen con este valor.",
+ "apihelp-query+alldeletedrevisions-param-tag": "Listar solo las revisiones con esta etiqueta.",
+ "apihelp-query+alldeletedrevisions-param-user": "Listar solo las revisiones de este usuario.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "No listar las revisiones de este usuario.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Listar solo las páginas en este espacio de nombres.",
+ "apihelp-query+alldeletedrevisions-example-user": "Listar las últimas 50 contribuciones borradas del usuario <kbd>Ejemplo<kbd>.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Listar las primeras 50 revisiones borradas en el espacio de nombres principal.",
+ "apihelp-query+allfileusages-description": "Listar todos los usos del archivo, incluyendo los que no existen.",
+ "apihelp-query+allimages-param-sha1": "Suma SHA1 de la imagen. Invalida $1sha1base36.",
+ "apihelp-query+allimages-param-sha1base36": "Suma SHA1 de la imagen en base 36 (usada en MediaWiki).",
+ "apihelp-query+alllinks-example-unique-generator": "Obtiene todos los títulos enlazados, marcando los que falten.",
+ "apihelp-query+allpages-example-B": "Mostrar una lista de páginas que empiecen con la letra <kbd>B</kbd>.",
+ "apihelp-query+allusers-param-activeusers": "Solo listar usuarios activos en {{PLURAL:$1|el último día|los $1 últimos días}}.",
+ "apihelp-query+backlinks-param-pageid": "Identificador de página que buscar. No puede usarse junto con <var>$1title</var>",
+ "apihelp-query+backlinks-example-simple": "Mostrar enlaces a la <kbd>Portada<kbd>.",
+ "apihelp-query+blocks-example-simple": "Listar bloques.",
+ "apihelp-query+categoryinfo-example-simple": "Obtener información acerca de <kbd>Category:Foo</kbd> y <kbd>Category:Bar</kbd>",
+ "apihelp-query+categorymembers-example-generator": "Obtener información sobre las primeras 10 páginas de la <kbd>Categoría:Física</kbd>",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Modo|Modos}}: $2",
+ "apihelp-query+deletedrevs-example-mode3-talk": "Listar las primeras 50 páginas en el espacio de nombres {{ns:talk}} (modo 3).",
+ "apihelp-query+duplicatefiles-example-simple": "Buscar duplicados de [[:File:Alber Einstein Head.jpg]].",
+ "apihelp-query+duplicatefiles-example-generated": "Buscar duplicados en todos los ficheros.",
+ "apihelp-query+exturlusage-example-simple": "Mostrar páginas que enlacen con <kbd>http://www.mediawiki.org</kbd>.",
+ "apihelp-query+filerepoinfo-example-simple": "Obtener información acerca de los repositorios de archivos.",
+ "apihelp-query+images-description": "Devuelve todos los archivos contenidos en las páginas dadas.",
+ "apihelp-query+images-example-simple": "Obtener una lista de los archivos usados en la [[Main Page|Portada]].",
+ "apihelp-query+imageusage-example-simple": "Mostrar las páginas que usan [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+imageusage-example-generator": "Obtener información sobre las páginas que empleen [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+info-example-protection": "Obtén información general y protección acerca de la página <kb>Página principal</kbd>.",
+ "apihelp-query+iwbacklinks-example-simple": "Obtener las páginas enlazadas a [[wikibooks:Test]]",
+ "apihelp-query+langbacklinks-example-simple": "Obtener las páginas enlazadas a [[:fr:Test]]",
+ "apihelp-query+linkshere-example-generator": "Obtener información acerca de las páginas enlazadas a la [[Main Page|Portada]].",
+ "apihelp-query+protectedtitles-example-generator": "Encuentra enlaces a títulos protegidos en el espacio de nombres principal.",
+ "apihelp-query+recentchanges-example-simple": "Lista de cambios recientes.",
+ "apihelp-query+redirects-example-simple": "Mostrar una lista de las redirecciones a la [[Main Page|Portada]]",
+ "apihelp-query+revisions-example-last5": "Mostrar las últimas 5 revisiones de la <kbd>Portada</kbd>.",
+ "apihelp-query+search-param-info": "Qué metadatos devolver.",
+ "apihelp-query+search-example-text": "Buscar <kbd>meaning</kbd> en los textos.",
+ "apihelp-query+siteinfo-example-simple": "Obtener información del sitio.",
+ "apihelp-query+usercontribs-example-user": "Mostrar contribuciones del usuario <kbd>Ejemplo</kbd>.",
+ "apihelp-query+usercontribs-example-ipprefix": "Mostrar las contribuciones de todas las direcciones IP con el prefijo <kbd>192.0.2.</kbd>.",
+ "apihelp-query+userinfo-description": "Obtener información sobre el usuario actual.",
+ "apihelp-query+watchlist-param-excludeuser": "No listar cambios de este usuario.",
+ "apihelp-query+watchlistraw-param-show": "Sólo listar los elementos que cumplen estos criterios.",
+ "apihelp-query+watchlistraw-example-simple": "Listar las páginas de la lista de seguimiento del usuario actual.",
+ "apihelp-unblock-example-user": "Desbloquear al usuario <kbd>Bob</kbd> con el motivo <kbd>Lo siento, Bob</kbd>",
+ "apihelp-undelete-example-revisions": "Restaurar dos revisiones de la página <kbd>Portada</kbd>.",
+ "apihelp-upload-param-watch": "Vigilar la página.",
+ "apihelp-upload-param-ignorewarnings": "Ignorar las advertencias.",
+ "apihelp-upload-example-url": "Subir desde una URL.",
+ "apihelp-userrights-param-user": "Nombre de usuario.",
+ "apihelp-userrights-param-add": "Agregar el usuario a estos grupos.",
+ "apihelp-userrights-param-remove": "Eliminar el usuario de estos grupos.",
+ "apihelp-userrights-param-reason": "Motivo del cambio.",
+ "apihelp-userrights-example-user": "Agregar al usuario <kbd>FooBot</kbd> al grupo <kbd>bot</kbd> y eliminarlo de los grupos <kbd>sysop</kbd> y <kbd>burócrata</kbd>.",
+ "apihelp-watch-example-watch": "Vigilar la página <kbd>Portada</kbd>.",
+ "apihelp-watch-example-unwatch": "Dejar de vigilar la <kbd>Portada</kbd>.",
+ "api-help-main-header": "Módulo principal",
+ "api-help-flag-deprecated": "Este módulo está en desuso.",
+ "api-help-flag-readrights": "Este módulo requiere permisos de lectura.",
+ "api-help-flag-writerights": "Este módulo requiere permisos de escritura.",
+ "api-help-flag-mustbeposted": "Este módulo solo acepta solicitudes POST.",
+ "api-help-flag-generator": "Este módulo puede utilizarse como un generador.",
+ "api-help-parameters": "{{PLURAL:$1|Parámetro|Parámetros}}:",
+ "api-help-param-deprecated": "En desuso.",
+ "api-help-param-required": "Este parámetro es obligatorio.",
+ "api-help-param-list": "{{PLURAL:$1|1=Un valor|2=Valores (separados por <kbd>{{!}}</kbd>)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Debe estar vacío|Puede estar vacío, o $2}}",
+ "api-help-param-multi-separate": "Separar los valores con <kbd>|</kbd>.",
+ "api-help-param-default": "Predeterminado: $1",
+ "api-help-param-default-empty": "Predeterminado: <span class=\"apihelp-empty\">(vacío)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(sin descripción)</span>",
+ "api-help-examples": "{{PLURAL:$1|Ejemplo|Ejemplos}}:",
+ "api-help-permissions": "{{PLURAL:$1|Permiso|Permisos}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Concedido a|Concedidos a}}: $2",
+ "api-credits-header": "Créditos",
+ "api-credits": "Desarrolladores de la API:\n* Roan Kattouw (desarrollador principal sep 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (creador, desarrollador principal sep 2006–sep 2007)\n* Brad Jorsch (desarrollador principal 2013–actualidad)\n\nEnvía comentarios, sugerencias y preguntas a mediawiki-api@lists.wikimedia.org\no reporta un error en https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/eu.json b/includes/api/i18n/eu.json
new file mode 100644
index 00000000..574bd2fe
--- /dev/null
+++ b/includes/api/i18n/eu.json
@@ -0,0 +1,57 @@
+{
+ "@metadata": {
+ "authors": [
+ "Subi"
+ ]
+ },
+ "apihelp-block-description": "Blokeatu erabiltzaile bat.",
+ "apihelp-createaccount-description": "Erabiltzaile kontu berria sortu.",
+ "apihelp-createaccount-param-email": "Erabiltzailearen helbide elektronikoa (aukerakoa).",
+ "apihelp-createaccount-param-realname": "Erabiltzailearen benetako izena (aukerakoa).",
+ "apihelp-delete-description": "Orrialde bat ezabatu.",
+ "apihelp-edit-description": "Orrialdeak sortu eta aldatu.",
+ "apihelp-edit-param-minor": "Aldaketa txikia.",
+ "apihelp-edit-example-edit": "Orrialde bat aldatu",
+ "apihelp-expandtemplates-param-title": "Orrialdearen izenburua.",
+ "apihelp-feedcontributions-param-year": "Urtetik aurrera (eta lehenagotik)",
+ "apihelp-feedcontributions-param-month": "Hilabetetik aurrera (eta lehenagotik)",
+ "apihelp-feedrecentchanges-param-hideminor": "Ezkutatu aldaketa txikiak.",
+ "apihelp-feedrecentchanges-param-hidebots": "Ezkutatu botek egindako aldaketak.",
+ "apihelp-feedrecentchanges-param-hideanons": "Ezkutatu erabiltzaile anonimoek egindako aldaketak.",
+ "apihelp-feedrecentchanges-param-hideliu": "Ezkutatu izena emandako erabiltzaileek egindako aldaketak.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Ezkutatu zainpeko aldaketak.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Ezkutatu zuk egindako aldaketak.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Iragazi etiketen arabera.",
+ "apihelp-feedrecentchanges-example-simple": "Erakutsi aldaketa berriak",
+ "apihelp-feedrecentchanges-example-30days": "Erakutsi aldaketa berriak 30 egunez",
+ "apihelp-imagerotate-description": "Irudi bat edo gehiago biratu.",
+ "apihelp-login-param-password": "Pasahitza.",
+ "apihelp-login-param-domain": "Domeinua (hautazkoa).",
+ "apihelp-login-example-login": "Saioa hasi",
+ "apihelp-move-description": "Orrialde bat mugitu",
+ "apihelp-protect-example-protect": "Orrialde bat babestu",
+ "apihelp-query+allusers-param-witheditsonly": "Bakarrik zerrendatu aldaketak egin dituzten erabiltzaileak.",
+ "apihelp-query+allusers-param-activeusers": "Bakarrik zerrendatu azken {{PLURAL:$1|eguneko|$1 egunetako}} erabiltzaile aktiboak.",
+ "apihelp-query+imageinfo-param-urlheight": "$1urlwidth-en antzekoa.",
+ "apihelp-query+imageusage-example-simple": "Erakutsi [[:File:Albert Einstein Head.jpg]] darabilten orriak",
+ "apihelp-query+prefixsearch-param-search": "Bilatu katea.",
+ "apihelp-query+protectedtitles-example-simple": "Zerrendatu babestutako izenburuak",
+ "apihelp-query+recentchanges-example-simple": "Zerrendatu aldaketa berriak.",
+ "apihelp-upload-example-url": "Igo URL batetik.",
+ "apihelp-userrights-param-reason": "Aldaketarako arrazoia.",
+ "api-help-main-header": "Modulu nagusia",
+ "api-help-flag-deprecated": "Modulu hau zaharkitua dago.",
+ "api-help-parameters": "{{PLURAL:$1|Parametroa|Parametroak}}:",
+ "api-help-param-deprecated": "Zaharkitua.",
+ "api-help-param-required": "Parametro hau beharrezkoa da.",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Hutsik egon behar du|Hutsik egon daiteke edo $2}}",
+ "api-help-param-limit": "Ez dira $1 baino gehiago onartzen.",
+ "api-help-param-limit2": "Ez dira $1 ($2 botentzat) baino gehiago onartzen.",
+ "api-help-param-default": "Lehenetsia: $1",
+ "api-help-param-default-empty": "Lehenetsia: <span class=\"apihelp-empty\">(hutsik)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(deskribapenik gabe)</span>",
+ "api-help-examples": "{{PLURAL:$1|Adibidea|Adibideak}}:",
+ "api-help-permissions": "{{PLURAL:$1|Baimena|Baimenak}}:",
+ "api-credits-header": "Kredituak",
+ "api-credits": "API garatzaileak:\n* Roan Kattouw (garatzaile nagusia, 2007ko ira.–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (sortzailea, garatzaile nagusia, 2006ko ira.–2007ko ira.)\n* Brad Jorsch (garatzaile nagusia 2013–gaur egun)\n\nMesedez, bidal iezazkiguzu zure iruzkinak, iradokizunak eta galderak mediawiki-api@lists.wikimedia.org helbidera edo bete ezazu errore-txostena https://phabricator.wikimedia.org/ helbidean."
+}
diff --git a/includes/api/i18n/fa.json b/includes/api/i18n/fa.json
new file mode 100644
index 00000000..d792fd6e
--- /dev/null
+++ b/includes/api/i18n/fa.json
@@ -0,0 +1,240 @@
+{
+ "@metadata": {
+ "authors": [
+ "Alirezaaa",
+ "Arash.pt",
+ "Fatemi127",
+ "Reza1615",
+ "KhabarNegar",
+ "Sahehco",
+ "Signal89",
+ "Mjbmr"
+ ]
+ },
+ "apihelp-main-param-action": "کدام عملیات را انجام دهد.",
+ "apihelp-main-param-format": "فرمت خروجی.",
+ "apihelp-main-param-curtimestamp": "برچسب زمان کنونی را در نتیجه قرار دهید.",
+ "apihelp-block-description": "بستن کاربر",
+ "apihelp-block-param-user": "نام کاربری، آدرس آی پی یا محدوده آی پی موردنظر شما برای بستن.",
+ "apihelp-block-param-reason": "دلیل بسته‌شدن",
+ "apihelp-block-param-anononly": "فقط بستن کاربران ناشناس (مانند غیرفعال‌کردن ویرایش‌های ناشناس این آی‌پی).",
+ "apihelp-block-param-nocreate": "جلوگیری از ایجاد حساب.",
+ "apihelp-block-param-autoblock": "به طور خودکار آخرین نشانی آی‌پی استفاده‌شده، و هر نشانی پس از آن که سعی می‌کند از آن داخل شود را ببند.",
+ "apihelp-block-param-noemail": "از کاربر در برابر ارسال ایمیل از طریق ویکی جلوگیری شود. (نیازمند دسترسی <code>blockemail</code> است).",
+ "apihelp-block-param-hidename": "نام کاربری را از سیاههٔ بستن پنهان کن. (نیازمند دسترسی <code>hideuser</code> است).",
+ "apihelp-block-param-allowusertalk": "به کاربر برای ویرایش صفحه بحث‌شان اجازه دهید (بسته به <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "اگر کاربر پیش از این مسدود شده‌است، مسدود موجود را بازنویسی کن.",
+ "apihelp-block-param-watchuser": "صفحه‌های کاربر و بحث کاربر نشانی آی‌پی یا کاربر را پی‌گیری کنید.",
+ "apihelp-block-example-ip-simple": "آی‌پی <kbd>۱۹۲٫۰٫۲٫۵</kbd> را برای سه روز همراه دلیل <kbd>برخورد اول</kbd> ببندید",
+ "apihelp-clearhasmsg-description": "پرچم <code>hasmsg</code> را برای کاربر جاری پاک کن.",
+ "apihelp-clearhasmsg-example-1": "پاک‌کردن پرچم <code>hasmsg</code> برای کاربر جاری",
+ "apihelp-compare-description": "تفاوت بین ۲ صفحه را بیابید.\n\nشما باید یک شماره بازبینی، یک عنوان صفحه، یا یک شناسه صفحه برای هر دو «از» و «به» مشخص کنید.",
+ "apihelp-compare-param-fromtitle": "عنوان اول برای مقایسه.",
+ "apihelp-compare-param-fromid": "شناسه صفحه اول برای مقایسه.",
+ "apihelp-compare-param-fromrev": "نسخه اول برای مقایسه.",
+ "apihelp-compare-param-totitle": "عنوان دوم برای مقایسه.",
+ "apihelp-compare-param-toid": "شناسه صفحه دوم برای مقایسه.",
+ "apihelp-compare-param-torev": "نسخه دوم برای مقایسه.",
+ "apihelp-compare-example-1": "ایجاد تفاوت بین نسخه 1 و 2",
+ "apihelp-createaccount-description": "ایجاد حساب کاربری",
+ "apihelp-createaccount-param-name": "نام کاربری.",
+ "apihelp-createaccount-param-password": "رمز عبور (نادیده گرفته می‌شود اگر <var>$1mailpassword</var> تنظیم شده‌باشد).",
+ "apihelp-createaccount-param-domain": "دامنه برای احراز هویت خارجی (اختیاری).",
+ "apihelp-createaccount-param-email": "آدرس ایمیل کاربر (اختیاری)",
+ "apihelp-createaccount-param-realname": "نام واقعی کاربر (اختیاری).",
+ "apihelp-createaccount-param-mailpassword": "اگر به هر مقداری تنظیم شود، یک رمز عبور تصادفی به کاربر ایمیل خواهد شد.",
+ "apihelp-createaccount-param-reason": "دلیل اختیاری برای ایجاد حساب کاربری جهت قرارگرفتن در سیاهه‌ها.",
+ "apihelp-createaccount-example-pass": "ایجاد کاربر <kbd>testuser</kbd> همراه رمز عبور <kbd>test123</kbd>",
+ "apihelp-createaccount-example-mail": "ایجاد کاربر <kbd>testmailuser</kbd> و ارسال یک رمز عبور تصادفی به ای‌میل.",
+ "apihelp-delete-description": "حذف صفحه",
+ "apihelp-delete-param-title": "عنوان صفحه‌ای که قصد حذفش را دارید. نمی‌تواند در کنار <var>$1pageid</var> استفاده شود.",
+ "apihelp-delete-param-pageid": "شناسه صفحه‌ای که قصد حذفش را دارید. نمی‌تواند در کنار <var>$1title</var> استفاده شود.",
+ "apihelp-delete-param-reason": "دلیل برای حذف. اگر تنظیم نشود، یک دلیل خودکار ساخته‌شده استفاده می‌شود.",
+ "apihelp-delete-param-watch": "افزودن صفحه به فهرست پی‌گیری کاربر فعلی",
+ "apihelp-delete-param-unwatch": "صفحه را از پی‌گیری‌تان حذف کنید.",
+ "apihelp-delete-example-simple": "حذف <kbd>صفحهٔ اصلی</kbd>",
+ "apihelp-delete-example-reason": "حذف <kbd>صفحهٔ اصلی</kbd> همراه دلیل <kbd>آماده‌سازی برای انتقال</kbd>",
+ "apihelp-disabled-description": "این پودمان غیرفعال شده است.",
+ "apihelp-edit-description": "ایجاد و ویرایش صفحه",
+ "apihelp-edit-param-title": "عنوان صفحه‌ای که قصد ویرایشش را دارید. نمی‌تواند در کنار <var>$1pageid</var> استفاده شود.",
+ "apihelp-edit-param-pageid": "شناسه صفحهٔ صفحه‌ای که می‌خواهید ویرایشش کنید. نمی‌تواند در کنار <var>$1title</var> استفاده شود.",
+ "apihelp-edit-param-section": "شماره بخش. <kbd>۰</kbd> برای بخش بالا، «<kbd>تازه</kbd>» برای یک بخش تازه.",
+ "apihelp-edit-param-sectiontitle": "عنوان برای بخش جدید.",
+ "apihelp-edit-param-text": "محتوای صفحه.",
+ "apihelp-edit-param-summary": "خلاصه را ویرایش کنید. همچنین عنوان بخش را زمانی که $1section=تازه و $1sectiontitle تنظیم نشده‌است.",
+ "apihelp-edit-param-minor": "ویرایش جزئی.",
+ "apihelp-edit-param-notminor": "ویرایش غیر جزئی.",
+ "apihelp-edit-param-bot": "علامت زدن این ویرایش به عنوان ویرایش ربات.",
+ "apihelp-edit-param-createonly": "اگر صفحه موجود بود، ویرایش نکن.",
+ "apihelp-edit-param-nocreate": "رها کردن خطا در صورتی که صفحه وجود ندارد.",
+ "apihelp-edit-param-watch": "افزودن صفحه به فهرست پی‌گیری شما",
+ "apihelp-edit-param-unwatch": "حذف صفحه از فهرست پی‌گیری شما",
+ "apihelp-edit-param-prependtext": "این متن را به ابتدای صفحه اضافه کنید. $1text را لغو می‌کند.",
+ "apihelp-edit-param-undo": "این بازبینی را برگردانید. $1text، $1prependtext و $1appendtext را باطل می‌کند.",
+ "apihelp-edit-param-undoafter": "همه بازبینی‌ها را از $1undo تا این یکی برگردانید. اگر تنظیم نشد، فقط یک بازبینی را برگردانید.",
+ "apihelp-edit-param-redirect": "اصلاح خودکار تغییرمسیرها.",
+ "apihelp-edit-example-edit": "ویرایش صفحه",
+ "apihelp-emailuser-description": "ایمیل به کاربر",
+ "apihelp-emailuser-param-target": "کاربر برای ارسال ایمیل به وی.",
+ "apihelp-emailuser-param-subject": "موضوع هدر.",
+ "apihelp-emailuser-param-text": "متن رایانه.",
+ "apihelp-emailuser-param-ccme": "ارسال یک نسخه از رایانه به شما.",
+ "apihelp-expandtemplates-description": "گسترش همه الگوها در ویکی نبشته",
+ "apihelp-expandtemplates-param-title": "عنوان صفحه",
+ "apihelp-expandtemplates-param-text": "تبدیل برای ویکی‌متن.",
+ "apihelp-feedcontributions-description": "خوراک مشارکت‌های یک کاربر را برمی‌گرداند.",
+ "apihelp-feedcontributions-param-feedformat": "فرمت خوراک.",
+ "apihelp-feedcontributions-param-namespace": "فیلتر شدن مشارکتها براساس فضای نام.",
+ "apihelp-feedcontributions-param-year": "از سال (و پیش از آن).",
+ "apihelp-feedcontributions-param-month": "از ماه (و پیش از آن).",
+ "apihelp-feedcontributions-param-tagfilter": "فیلتر کردن مشارکتها براساس این برچسب‌ها.",
+ "apihelp-feedcontributions-param-deletedonly": "فقط مشارکت‌های حذف شده نمایش داده شود.",
+ "apihelp-feedcontributions-param-toponly": "فقط ویرایش‌هایی که آخرین نسخه‌اند نمایش داده شود.",
+ "apihelp-feedcontributions-param-newonly": "فقط نمایش ویرایش‌هایی که تولید‌های صفحه هستند.",
+ "apihelp-feedcontributions-param-showsizediff": "نمایش تفاوت حجم تغییرات بین نسخه‌ها.",
+ "apihelp-feedcontributions-example-simple": "مشارکت‌های [[کاربر:نمونه]] را برگردان",
+ "apihelp-feedrecentchanges-description": "خوراک تغییرات اخیر را برمی‌گرداند.",
+ "apihelp-feedrecentchanges-param-feedformat": "فرمت خوراک.",
+ "apihelp-feedrecentchanges-param-namespace": "فضای نام برای محدودکردن نتایج به.",
+ "apihelp-feedrecentchanges-param-invert": "همهٔ فضاهای نام به جز انتخاب‌شده‌ها.",
+ "apihelp-feedrecentchanges-param-associated": "فضای نام مرتبط (بحث یا اصلی) را شامل می‌شود.",
+ "apihelp-feedrecentchanges-param-days": "روز برای محدود کردن نتایج.",
+ "apihelp-feedrecentchanges-param-limit": "حداکثر تعداد نتایج خروجی.",
+ "apihelp-feedrecentchanges-param-from": "نمایش تغییرات پس از آن.",
+ "apihelp-feedrecentchanges-param-hideminor": "پنهان کردن تغییرات جزئی.",
+ "apihelp-feedrecentchanges-param-hidebots": "پنهان کردن تغییرات انجام شده توسط ربات‌ها.",
+ "apihelp-feedrecentchanges-param-hideanons": "پنهان کردن ویرایش‌های کاربران ناشناس.",
+ "apihelp-feedrecentchanges-param-hideliu": "پنهان کردن ویرایش‌های کاربران ثبت‌نام کرده.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "پنهان کردن ویرایش گشت‌زن‌ها.",
+ "apihelp-feedrecentchanges-param-hidemyself": "پنهان کردن ویرایش‌های کاربر فعلی.",
+ "apihelp-feedrecentchanges-param-tagfilter": "فیلتر کردن براساس برچسب",
+ "apihelp-feedrecentchanges-param-target": "فقط نمایش ویرایش‌هایی که پیوند دارند به این صفحه.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "نمایش ویرایش‌ها بر روی صفحات پیوند داده شده به صفحات انتخاب شده.",
+ "apihelp-feedrecentchanges-example-simple": "نمایش تغییرات اخیر",
+ "apihelp-feedrecentchanges-example-30days": "نمایش تغییرات اخیر در 30 روز اخیر",
+ "apihelp-feedwatchlist-description": "برگرداندن فهرست پیگیری‌های خوراک.",
+ "apihelp-feedwatchlist-param-feedformat": "فرمت خوراک.",
+ "apihelp-feedwatchlist-param-linktosections": "اگر ممکن است به طور مستقیم به بخش‌های تغییریافته پیوند دهید.",
+ "apihelp-feedwatchlist-example-default": "نمایش خوراک فهرست پی‌گیری",
+ "apihelp-feedwatchlist-example-all6hrs": "همهٔ تغییرات ۶ ساعت گذشته در صفحه‌های پی‌گیری را نمایش دهید",
+ "apihelp-filerevert-description": "واگردانی فایل به یک نسخه قدیمی",
+ "apihelp-filerevert-param-filename": "نام پروندهٔ مقصد، بدون پیشوند پرونده:.",
+ "apihelp-filerevert-param-comment": "ارسال دیدگاه.",
+ "apihelp-filerevert-param-archivename": "نام بایگانی بازبینی برای برگرداندن.",
+ "apihelp-filerevert-example-revert": "برگرداندن <kbd>Wiki.png</kbd> به نسخهٔ <kbd>2011-03-05T15:27:40Z</kbd>",
+ "apihelp-help-description": "راهنما برای پودمان‌های مشخص‌شده را نمایش دهید.",
+ "apihelp-help-param-helpformat": "قالب‌بندی خروجی راهنما.",
+ "apihelp-help-example-main": "راهنما برای پودمان اصلی",
+ "apihelp-help-example-recursive": "همهٔ راهنما در یک صفحه",
+ "apihelp-help-example-help": "راهنما برای خود ماژول راهنما",
+ "apihelp-help-example-query": "راهنما برای دو زیر پودمان کوئری",
+ "apihelp-imagerotate-description": "چرخاندن یک یا چند تصویر",
+ "apihelp-imagerotate-param-rotation": "درجه برای چرخاندن تصویر در جهت ساعت‌گرد.",
+ "apihelp-imagerotate-example-simple": "چرخاندن <kbd>۹۰</kbd> درجه برای <kbd>File:Example.png</kbd>",
+ "apihelp-imagerotate-example-generator": "چرخاندن <kbd>۱۸۰</kbd> درجه برای همهٔ تصاویر موجود در <kbd>Category:Flip</kbd>",
+ "apihelp-import-param-summary": "خلاصه درون‌ریزی.",
+ "apihelp-import-param-xml": "پرونده XML بارگذاری شد.",
+ "apihelp-import-param-interwikisource": "برای درون‌ریز میان‌ویکی: ویکی برای درون‌ریزی از.",
+ "apihelp-import-param-interwikipage": "برای درون‌ریز میان‌ویکی: صفحه برای درون‌ریزی.",
+ "apihelp-import-param-fullhistory": "برای درون‌ریزی میان‌ویکی: درون‌ریزی تاریخچهٔ کامل، نه فقط نسخهٔ موجود.",
+ "apihelp-import-param-templates": "برای درون ریزی میان‌ویکی: همچنین درون‌ریزی الگوهای مورد استفاده.",
+ "apihelp-import-param-namespace": "برای درون‌ریزی میان‌ویکی: درون‌ریزی به این فضای نام.",
+ "apihelp-import-param-rootpage": "درون‌ریزی به عنوان زیر صفحهٔ این صفحه.",
+ "apihelp-login-param-name": "نام کاربری.",
+ "apihelp-login-param-password": "گذرواژه.",
+ "apihelp-login-param-domain": "دامنه (اختیاری)",
+ "apihelp-login-example-gettoken": "دریافت توکن ورود",
+ "apihelp-login-example-login": "ورود",
+ "apihelp-logout-description": "خروج به همراه پاک نمودن اطلاعات این نشست",
+ "apihelp-logout-example-logout": "خروج کاربر فعلی",
+ "apihelp-move-description": "انتقال صفحه",
+ "apihelp-move-param-to": "عنوانی که قصد دارید صفحه را به آن نام تغییر دهید.",
+ "apihelp-move-param-reason": "دلیل انتقال",
+ "apihelp-move-param-movetalk": "صفحهٔ بحث را تغییرنام دهید، اگر وجوددارد.",
+ "apihelp-move-param-movesubpages": "زیرصفحه را تغییرنام دهید، اگر شدنی است.",
+ "apihelp-move-param-noredirect": "عدم ساخت تغییرمسیر.",
+ "apihelp-move-param-watch": "صفحه و تغییرمسیر را به پی‌گیری کاربر کنونی بیافزایید.",
+ "apihelp-move-param-unwatch": "صفحه و تغییرمسیر را از پی‌گیری کاربر کنونی حذف کنید.",
+ "apihelp-move-param-ignorewarnings": "چشم‌پوشی از همهٔ هشدارها.",
+ "apihelp-opensearch-description": "جستجو در ویکی بااستفاده از پروتکل اوپن‌سرچ.",
+ "apihelp-opensearch-param-search": "جستجوی رشته.",
+ "apihelp-opensearch-param-limit": "حداکثر تعداد نتایج برای بازگرداندن.",
+ "apihelp-opensearch-param-namespace": "فضاهای نامی برای جستجو",
+ "apihelp-opensearch-param-suggest": "کاری نکنید اگر <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> false است.",
+ "apihelp-opensearch-param-format": "فرمت خروجی.",
+ "apihelp-opensearch-example-te": "یافتن صفحه‌هایی که با <kbd>ته</kbd> آغاز می‌شوند",
+ "apihelp-options-example-reset": "بازنشانی همه تنظیمات.",
+ "apihelp-paraminfo-param-helpformat": "ساختار راهنمای رشته‌ها",
+ "apihelp-parse-example-page": "تجزیه یک صفحه.",
+ "apihelp-parse-example-text": "تجزیه متن ویکی.",
+ "apihelp-parse-example-summary": "تجزیه خلاصه.",
+ "apihelp-patrol-description": "گشت‌زنی یک صفحه یا نسخهٔ ویرایشی.",
+ "apihelp-patrol-example-rcid": "گشت‌زنی یک تغییر اخیر",
+ "apihelp-patrol-example-revid": "گشت‌زدن یک نسخه",
+ "apihelp-protect-description": "تغییر سطح محافظت صفحه",
+ "apihelp-protect-param-reason": "دلیل برای (عدم) حفاظت.",
+ "apihelp-protect-example-protect": "محافظت از صفحه",
+ "apihelp-protect-example-unprotect": "خارج ساختن صفحه از حفاظت با تغییر سطح حفاظتی به <kbd>همگان</kbd>.",
+ "apihelp-protect-example-unprotect2": "خارج ساختن صفحه از حفاظت با قراردادن هیچ‌گونه محدودیت‌حفاظتی",
+ "apihelp-purge-param-forcelinkupdate": "به‌روزرسانی جداول پیوندها.",
+ "apihelp-purge-param-forcerecursivelinkupdate": "جدول پیوندها را به‌روز رسانی کنید، و جدول‌های پیوندهای هر صفحه‌ای را که از این صفحه به عنوان الگو استفاده می‌کند به‌روز رسانی کنید.",
+ "apihelp-query-param-list": "کدام فهرست‌ها دریافت شود.",
+ "apihelp-query-param-meta": "کدام فراداده‌ها دریافت شود.",
+ "apihelp-query+allcategories-param-prefix": "عنوان همهٔ رده‌ها را که با این مقدار آغاز می‌شود جستجو کنید.",
+ "apihelp-query+allcategories-param-limit": "میزان رده‌ها برای بازگرداندن.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "نمی‌تواند همراه <var>$3user</var> به کار رود.",
+ "apihelp-query+allfileusages-param-limit": "تعداد آیتم‌ها برای بازگرداندن.",
+ "apihelp-query+allfileusages-param-dir": "جهتی که باید فهرست شود.",
+ "apihelp-query+allfileusages-example-unique": "فهرست پرونده‌های با عنوان یکتا",
+ "apihelp-query+allfileusages-example-unique-generator": "گرفتن عنوان همهٔ پرونده‌ها، برچسب زدن موارد گم شده",
+ "apihelp-query+allfileusages-example-generator": "گرفتن صفحاتی که دارای پرونده هستند",
+ "apihelp-query+allimages-description": "متوالی شمردن همهٔ تصاویر.",
+ "apihelp-query+allimages-param-sort": "خصوصیت برای مرتب‌سازی بر پایه آن",
+ "apihelp-query+allimages-param-dir": "جهتی که باید فهرست شود.",
+ "apihelp-query+allimages-param-minsize": "محدودکردن به صفحه‌هایی که دست کم این تعداد بایت دارند.",
+ "apihelp-query+allimages-param-maxsize": "محدودکردن به صفحه‌هایی که حداکثر این تعداد بایت دارند.",
+ "apihelp-query+alllinks-param-namespace": "فضای نامی که باید شمرده شود.",
+ "apihelp-query+alllinks-param-limit": "تعداد آیتم‌ها برای بازگرداندن.",
+ "apihelp-query+alllinks-param-dir": "جهتی که باید فهرست شود.",
+ "apihelp-query+allpages-param-filterredir": "صفحه‌هایی که باید فهرست شوند.",
+ "apihelp-query+allpages-param-minsize": "محدودکردن به صفحه‌هایی که همراه دست کم این تعداد بایت است.",
+ "apihelp-query+allpages-param-limit": "میزان کل صفحه‌ها برای بازگرداندن.",
+ "apihelp-query+allredirects-param-limit": "تعداد آیتم‌ها برای بازگرداندن.",
+ "apihelp-query+backlinks-example-simple": "نمایش پیوندها به <kbd>صفحهٔ اصلی<kbd>",
+ "apihelp-query+blocks-example-simple": "فهرست بسته‌شده‌ها",
+ "apihelp-query+categories-param-show": "کدام نوع رده‌ها نمایش داده‌شود.",
+ "apihelp-query+categories-param-limit": "چه میزان رده بازگردانده شود.",
+ "apihelp-query+categories-param-categories": "فقط این رده‌ها فهرست شود. کاربردی برای بررسی وجود یک صفحهٔ مشخص در یک ردهٔ مشخص.",
+ "apihelp-query+categorymembers-description": "فهرست‌کردن همهٔ صفحه‌ها در یک ردهٔ مشخص‌شده.",
+ "apihelp-query+categorymembers-param-sort": "خصوصیت برای مرتب‌سازی",
+ "apihelp-query+categorymembers-param-dir": "جهت مرتب شدن",
+ "apihelp-query+categorymembers-param-startsortkey": "جایش از $1starthexsortkey استفاده کنید.",
+ "apihelp-query+imageinfo-param-urlheight": "مشابه $1urlwidth.",
+ "apihelp-query+info-description": "دریافت اطلاعات سادهٔ صفحه.",
+ "apihelp-query+iwbacklinks-param-prefix": "پیشوند میان‌ویکی.",
+ "apihelp-query+iwbacklinks-param-title": "پیوند میان‌ویکی برای جستجو. باید همراه <var>$1blprefix</var> استفاده شود.",
+ "apihelp-query+iwbacklinks-param-limit": "تعداد صفحه‌ها برای بازگرداندن.",
+ "apihelp-query+linkshere-param-limit": "تعداد برای بازگرداندن.",
+ "apihelp-query+logevents-description": "دریافت رویدادها از سیاهه‌ها.",
+ "apihelp-query+prefixsearch-param-search": "جستجوی رشته",
+ "apihelp-query+prefixsearch-param-namespace": "فضاهای نامی برای جستجو",
+ "apihelp-query+prefixsearch-param-limit": "حداکثر تعداد نتایج برای بازگرداندن.",
+ "apihelp-query+prefixsearch-param-offset": "تعداد نتایج برای رها کردن.",
+ "apihelp-query+protectedtitles-param-namespace": "فقط عنوان‌ها در این فضاهای نام را فهرست کنید.",
+ "apihelp-query+protectedtitles-param-level": "فقط عنوان‌ها در این سطح‌های حفاظت را فهرست کنید.",
+ "apihelp-query+protectedtitles-param-limit": "تعداد صفحه‌ها برای بازگرداندن.",
+ "apihelp-query+protectedtitles-param-start": "آغاز فهرست‌کردن از این برچسب زمانی حفاظت.",
+ "apihelp-query+protectedtitles-param-end": "متوقف‌کردن فهرست‌کردن در این برچسب زمانی حفاظت.",
+ "apihelp-query+random-param-namespace": "بازگرداندن صفحه‌های فقط در این فضاهای نام.",
+ "apihelp-query+random-param-limit": "محدود کنید چه تعداد صفحه بازگردانده خواهد شد.",
+ "apihelp-query+random-param-redirect": "یک تغییرمسیر تصادفی جای یک صفحه تصادفی بارگیری کنید.",
+ "apihelp-query+random-example-simple": "بازگرداندن تو صفحهٔ تصادفی از فضای نام اصلی",
+ "apihelp-query+random-example-generator": "بازگرداندن اطلاعات صفحه دربارهٔ دو صفحهٔ تصادفی از فضای نام اصلی",
+ "apihelp-query+recentchanges-param-start": "برچسب زمانی برای آغاز شمارش از.",
+ "apihelp-query+recentchanges-param-end": "برچسب زمانی برای پایان شمارش.",
+ "apihelp-query+redirects-param-limit": "تعداد تغییرمسیرها برای بازگرداندن.",
+ "apihelp-upload-param-ignorewarnings": "چشم‌پوشی از همهٔ هشدارها.",
+ "apihelp-userrights-param-user": "نام کاربری.",
+ "api-help-param-deprecated": "توصیه.",
+ "api-credits-header": "اعتبار"
+}
diff --git a/includes/api/i18n/fi.json b/includes/api/i18n/fi.json
new file mode 100644
index 00000000..b1a7e8c4
--- /dev/null
+++ b/includes/api/i18n/fi.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Nike",
+ "MrTapsa"
+ ]
+ },
+ "apihelp-query+linkshere-param-show": "Näytä vain kohteet, jotka täyttävät nämä kriteerit:\n;redirect:Näytä vain uudelleenohjaukset.\n;!redirect:Näytä vain ei-uudelleenohjaukset",
+ "apihelp-upload-param-stash": "Mikäli valittu, palvelin säilöö tiedoston väliaikaisesti tallentamisen sijaan."
+}
diff --git a/includes/api/i18n/fr.json b/includes/api/i18n/fr.json
new file mode 100644
index 00000000..114e36c2
--- /dev/null
+++ b/includes/api/i18n/fr.json
@@ -0,0 +1,1054 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gomoko",
+ "Windes",
+ "Orlodrim",
+ "McDutchie",
+ "Element303",
+ "Macofe",
+ "Linedwell",
+ "Nicolapps",
+ "Raulel",
+ "Arkanosis",
+ "Ltrlg"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> Toutes les fonctionnalités affichées sur cette page devraient fonctionner, mais l’API est encore en cours de développement et peut changer à tout moment. Inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un en-tête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet en-tête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:API:Errors_and_warnings|API: Errors and warnings]].",
+ "apihelp-main-param-action": "Quelle action effectuer.",
+ "apihelp-main-param-format": "Le format de sortie.",
+ "apihelp-main-param-maxlag": "La latence maximale peut être utilisée quand MédiaWiki est installé sur un cluster de base de données répliqué. Pour éviter des actions provoquant un supplément de latence de réplication de site, ce paramètre peut faire attendre le client jusqu’à ce que la latence de réplication soit inférieure à une valeur spécifiée. En cas de latence excessive, le code d’erreur <samp>maxlag</samp> est renvoyé avec un message tel que <samp>Attente de $host : $lag secondes de délai</samp>.<br />Voyez [[mw:Manual:Maxlag_parameter|Manuel: Maxlag parameter]] pour plus d’information.",
+ "apihelp-main-param-smaxage": "Fixer l’entête <code>s-maxage</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
+ "apihelp-main-param-maxage": "Fixer l’entête <code>max-age</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
+ "apihelp-main-param-assert": "Vérifier si l’utilisateur est connecté si positionné à <kbd>user</kbd>, ou a le droit utilisateur robot si positionné à <kbd>bot</kbd>.",
+ "apihelp-main-param-requestid": "Toute valeur fournie ici sera incluse dans la réponse. Peut être utilisé pour distinguer des demandes.",
+ "apihelp-main-param-servedby": "Inclure le nom d’hôte qui a renvoyé la requête dans les résultats.",
+ "apihelp-main-param-curtimestamp": "Inclure l’horodatage actuel dans le résultat.",
+ "apihelp-main-param-origin": "En accédant à l’API en utilisant une requête AJAX inter-domaines (CORS), mettre le domaine d’origine dans ce paramètre. Il doit être inclus dans toute requête de pre-flight, et doit donc faire partie de l’URI de la requête (pas du corps du POST). Il doit correspondre exactement à une des origines dans l’entête <code>Origin</code> header, donc il doit être fixé avec quelque chose comme <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Si ce paramètre ne correspond pas à l’entête <code>Origin</code>, une réponse 403 sera renvoyée. Si ce paramètre correspond à l’entête <code>Origin</code> et que l’origine est en liste blanche, un entête <code>Access-Control-Allow-Origin</code> sera positionné.",
+ "apihelp-main-param-uselang": "Langue à utiliser pour les traductions de message. Une liste de codes peut être analysée depuis <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> avec <kbd>siprop=languages</kbd>, ou en spécifiant <kbd>user</kbd> pour utiliser la préférence de langue de l’utilisateur actuel, ou en spécifiant <kbd>content</kbd> pour utiliser le langage du contenu de ce wiki.",
+ "apihelp-block-description": "Bloquer un utilisateur.",
+ "apihelp-block-param-user": "Nom d’utilisateur, adresse IP ou plage d’adresses IP que vous voulez bloquer.",
+ "apihelp-block-param-expiry": "Durée d’expiration. Peut être relative (par ex. <kbd>5 months</kbd> ou <kbd>2 weeks</kbd>) ou absolue (par ex. <kbd>2014-09-18T12:34:56Z</kbd>). Si elle est mise à <kbd>infinite</kbd>, <kbd>indefinite</kbd> ou <kbd>never</kbd>, le blocage n’expirera jamais.",
+ "apihelp-block-param-reason": "Motif du blocage.",
+ "apihelp-block-param-anononly": "Bloquer uniquement les utilisateurs anonymes (c’est-à-dire désactiver les modifications anonymes pour cette adresse IP).",
+ "apihelp-block-param-nocreate": "Empêcher la création de compte.",
+ "apihelp-block-param-autoblock": "Bloquer automatiquement la dernière adresse IP utilisée, et toute les adresses IP subséquentes depuis lesquelles ils ont essayé de se connecter.",
+ "apihelp-block-param-noemail": "Empêcher l’utilisateur d’envoyer des courriels via le wiki (nécessite le doit <code>blockemail</code>).",
+ "apihelp-block-param-hidename": "Masque le nom de l’utilisateur dans le journal des blocages (nécessite le droit <code>hideuser</code>).",
+ "apihelp-block-param-allowusertalk": "Autoriser les utilisateurs à modifier leur propre page de discussion (dépend de <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "Si l’utilisateur est déjà bloqué, écraser le blocage existant.",
+ "apihelp-block-param-watchuser": "Surveiller les pages utilisateur et de discussion de l’utilisateur ou de l’adresse IP.",
+ "apihelp-block-example-ip-simple": "Bloquer l’adresse IP <kbd>192.0.2.5</kbd> pour trois jours avec le motif <kbd>Premier avertissement</kbd>.",
+ "apihelp-block-example-user-complex": "Bloquer indéfiniment l’utilisateur <kbd>Vandale</kbd> avec le motif <kbd>Vandalisme</kbd>, et empêcher la création de nouveau compte et l'envoi de courriel.",
+ "apihelp-checktoken-description": "Vérifier la validité d'un jeton de <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
+ "apihelp-checktoken-param-type": "Type de jeton testé",
+ "apihelp-checktoken-param-token": "Jeton à tester.",
+ "apihelp-checktoken-param-maxtokenage": "Temps maximum autorisé pour le jeton, en secondes",
+ "apihelp-checktoken-example-simple": "Tester la validité d'un jeton de <kbd>csrf</kbd>.",
+ "apihelp-clearhasmsg-description": "Efface le drapeau <code>hasmsg</code> pour l’utilisateur courant.",
+ "apihelp-clearhasmsg-example-1": "Effacer le drapeau <code>hasmsg</code> pour l’utilisateur courant",
+ "apihelp-compare-description": "Obtenir la différence entre 2 pages.\n\nVous devez passer un numéro de révision, un titre de page, ou un ID de page, à la fois pour « from » et « to ».",
+ "apihelp-compare-param-fromtitle": "Premier titre à comparer.",
+ "apihelp-compare-param-fromid": "ID de la première page à comparer.",
+ "apihelp-compare-param-fromrev": "Première révision à comparer.",
+ "apihelp-compare-param-totitle": "Second titre à comparer.",
+ "apihelp-compare-param-toid": "ID de la seconde page à comparer.",
+ "apihelp-compare-param-torev": "Seconde révision à comparer.",
+ "apihelp-compare-example-1": "Créer une différence entre les révisions 1 et 2",
+ "apihelp-createaccount-description": "Créer un nouveau compte utilisateur.",
+ "apihelp-createaccount-param-name": "Nom d’utilisateur.",
+ "apihelp-createaccount-param-password": "Mot de passe (ignoré si <var>$1mailpassword</var> est défini).",
+ "apihelp-createaccount-param-domain": "Domaine pour l’authentification externe (facultatif).",
+ "apihelp-createaccount-param-token": "Jeton de création de compte obtenu à la première requête.",
+ "apihelp-createaccount-param-email": "Adresse de courriel de l’utilisateur (facultatif).",
+ "apihelp-createaccount-param-realname": "Vrai nom de l’utilisateur (facultatif).",
+ "apihelp-createaccount-param-mailpassword": "S’il est fixé à une valeur quelconque, un mot de passe aléatoire sera envoyé par courriel à l’utilisateur.",
+ "apihelp-createaccount-param-reason": "Motif facultatif de création du compte à mettre dans les journaux.",
+ "apihelp-createaccount-param-language": "Code de langue à mettre par défaut pour l’utilisateur (facultatif, par défaut langue du contenu).",
+ "apihelp-createaccount-example-pass": "Créer l’utilisateur <kbd>testuser</kbd> avec le mot de passe <kbd>test123</kbd>.",
+ "apihelp-createaccount-example-mail": "Créer l’utilisateur <kbd>testmailuser</kbd> et envoyer par courriel un mot de passe généré aléatoirement.",
+ "apihelp-delete-description": "Supprimer une page.",
+ "apihelp-delete-param-title": "Titre de la page que vous voulez supprimer. Impossible de l’utiliser avec <var>$1pageid</var>.",
+ "apihelp-delete-param-pageid": "ID de la page que vous voulez supprimer. Impossible à utiliser avec <var>$1title</var>.",
+ "apihelp-delete-param-reason": "Motif de suppression. Si non défini, un motif généré automatiquement sera utilisé.",
+ "apihelp-delete-param-watch": "Ajouter la page à la liste de suivi de l’utilisateur actuel.",
+ "apihelp-delete-param-watchlist": "Ajouter ou supprimer sans distinction la page de la liste de suivi de l'utilisateur actuel, utiliser les préférences ou ne rien changer au suivi.",
+ "apihelp-delete-param-unwatch": "Supprimer la page de la liste de suivi de l'utilisateur actuel.",
+ "apihelp-delete-param-oldimage": "Le nom de l’ancienne image à supprimer tel que fourni par [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]].",
+ "apihelp-delete-example-simple": "Supprimer <kbd>Page principale</kbd>.",
+ "apihelp-delete-example-reason": "Supprimer <kbd>Page principale</kbd> avec le motif <kbd>Préparation au déplacement</kbd>",
+ "apihelp-disabled-description": "Ce module a été désactivé.",
+ "apihelp-edit-description": "Créer et modifier les pages.",
+ "apihelp-edit-param-title": "Titre de la page que vous voulez modifier. Impossible de l’utiliser avec <var>$1pageid</var>.",
+ "apihelp-edit-param-pageid": "ID de la page que vous voulez modifier. Impossible à utiliser avec <var>$1title</var>.",
+ "apihelp-edit-param-section": "Numéro de section. <kbd>0</kbd> pour la section de tête, <kbd>new</kbd> pour une nouvelle section.",
+ "apihelp-edit-param-sectiontitle": "Le titre pour une nouvelle section.",
+ "apihelp-edit-param-text": "Contenu de la page.",
+ "apihelp-edit-param-summary": "Modifier le résumé. Également le titre de la section quand $1section=new et $1sectiontitle n’est pas défini.",
+ "apihelp-edit-param-minor": "Modification mineure.",
+ "apihelp-edit-param-notminor": "Modification non mineure.",
+ "apihelp-edit-param-bot": "Marquer cette modification comme robot.",
+ "apihelp-edit-param-basetimestamp": "Horodatage de la révision de base, utilisé pour détecter les conflits de modification. Peut être obtenu via [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
+ "apihelp-edit-param-starttimestamp": "L'horodatage, lorsque le processus d'édition est démarré, est utilisé pour détecter les conflits de modification. Une valeur appropriée peut être obtenue en utilisant <var>[[Special:ApiHelp/main|curtimestamp]]</var> lors du démarrage du processus d'édition (par ex. en chargeant le contenu de la page à modifier).",
+ "apihelp-edit-param-recreate": "Ignorer toutes les erreurs concernant la page \nqui a été supprimée entre-temps.",
+ "apihelp-edit-param-createonly": "Ne pas modifier la page si elle existe déjà.",
+ "apihelp-edit-param-nocreate": "Lever une erreur si la page n’existe pas.",
+ "apihelp-edit-param-watch": "Ajouter la page à la liste de suivi de l'utilisateur actuel.",
+ "apihelp-edit-param-unwatch": "Supprimer la page de la liste de suivi de l'utilisateur actuel.",
+ "apihelp-edit-param-watchlist": "Ajouter ou supprimer sans condition la page de votre liste de suivi, utiliser les préférences ou ne pas changer le suivi.",
+ "apihelp-edit-param-md5": "Le hachage MD5 du paramètre $1text, ou les paramètres $1prependtext et $1appendtext concaténés. Si défini, la modification ne sera pas effectuée à moins que le hachage ne soit correct.",
+ "apihelp-edit-param-prependtext": "Ajouter ce texte au début de la page. Écrase $1text.",
+ "apihelp-edit-param-appendtext": "Ajouter ce texte à la fin de la page. Écrase $1text.\n\nUtiliser $1section=new pour ajouter une nouvelle section, plutôt que ce paramètre.",
+ "apihelp-edit-param-undo": "Annuler cette révision. Écrase $1text, $1prependtext et $1appendtext.",
+ "apihelp-edit-param-undoafter": "Annuler toutes les révisions depuis $1undo jusqu’à celle-ci. Si non défini, annuler uniquement une révision.",
+ "apihelp-edit-param-redirect": "Résoudre automatiquement les redirections.",
+ "apihelp-edit-param-contentformat": "Format de sérialisation du contenu utilisé pour le texte d’entrée.",
+ "apihelp-edit-param-contentmodel": "Modèle de contenu du nouveau contenu.",
+ "apihelp-edit-param-token": "Le jeton doit toujours être envoyé en tant que dernier paramètre, ou au moins après le paramètre $1text.",
+ "apihelp-edit-example-edit": "Modifier une page",
+ "apihelp-edit-example-prepend": "Préfixer une page par <kbd>_&#95;NOTOC_&#95;</kbd>",
+ "apihelp-edit-example-undo": "Annuler les révisions 13579 à 13585 avec résumé automatique",
+ "apihelp-emailuser-description": "Envoyer un courriel à un utilisateur.",
+ "apihelp-emailuser-param-target": "Utilisateur à qui envoyer le courriel.",
+ "apihelp-emailuser-param-subject": "Entête du sujet.",
+ "apihelp-emailuser-param-text": "Corps du courriel.",
+ "apihelp-emailuser-param-ccme": "M’envoyer une copie de ce courriel.",
+ "apihelp-emailuser-example-email": "Envoyer un courriel à l’utilisateur <kbd>WikiSysop</kbd> avec le texte <kbd>Contenu</kbd>.",
+ "apihelp-expandtemplates-description": "Développe tous les modèles en wikitexte.",
+ "apihelp-expandtemplates-param-title": "Titre de la page.",
+ "apihelp-expandtemplates-param-text": "Wikitexte à convertir.",
+ "apihelp-expandtemplates-param-revid": "ID de révision, pour <nowiki>{{REVISIONID}}</nowiki> et les variables semblables.",
+ "apihelp-expandtemplates-param-prop": "Quelles informations récupérer :\n;wikitext:Le wikitexte développé.\n;categories:Toutes les catégories présentes dans l’entrée qui ne sont pas représentées dans le wikitexte de sortie.\n;properties:Propriétés de page définies en développant les mots magiques dans le wikitexte.\n;volatile:Si la sortie est volatile et ne devrait pas être réutilisée ailleurs dans la page.\n;ttl:Le délai maximal après lequel les caches du résultat devraient être invalidés.\n;parsetree:L’arbre d’analyse XML de l’entrée.\nNoter que si aucune valeur n’est sélectionnée, le résultat contiendra le wikitexte, mais la sortie sera dans un format obsolète.",
+ "apihelp-expandtemplates-param-includecomments": "S’il faut inclure les commentaires HTML dans la sortie.",
+ "apihelp-expandtemplates-param-generatexml": "Générer l’arbre d’analyse XML (remplacé par $1prop=parsetree).",
+ "apihelp-expandtemplates-example-simple": "Développe le wikitexte <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>.",
+ "apihelp-feedcontributions-description": "Renvoie le fil des contributions d’un utilisateur.",
+ "apihelp-feedcontributions-param-feedformat": "Le format du flux.",
+ "apihelp-feedcontributions-param-user": "Pour quels utilisateurs récupérer les contributions.",
+ "apihelp-feedcontributions-param-namespace": "Par quels espaces de nom filtrer les contributions.",
+ "apihelp-feedcontributions-param-year": "Depuis l’année (et plus récent).",
+ "apihelp-feedcontributions-param-month": "Depuis le mois (et plus récent).",
+ "apihelp-feedcontributions-param-tagfilter": "Filtrer les contributions qui ont ces balises.",
+ "apihelp-feedcontributions-param-deletedonly": "Afficher uniquement les contributions supprimées.",
+ "apihelp-feedcontributions-param-toponly": "Afficher uniquement les modifications qui sont les dernières révisions.",
+ "apihelp-feedcontributions-param-newonly": "Afficher uniquement les modifications qui sont des créations de page.",
+ "apihelp-feedcontributions-param-showsizediff": "Afficher la différence de taille entre les révisions.",
+ "apihelp-feedcontributions-example-simple": "Renvoyer les contributions de l'utilisateur <kbd>Exemple</kbd>.",
+ "apihelp-feedrecentchanges-description": "Renvoie un fil de modifications récentes.",
+ "apihelp-feedrecentchanges-param-feedformat": "Le format du flux.",
+ "apihelp-feedrecentchanges-param-namespace": "Espace de noms auquel limiter les résultats.",
+ "apihelp-feedrecentchanges-param-invert": "Tous les espaces de nom sauf le sélectionné.",
+ "apihelp-feedrecentchanges-param-associated": "Inclure l’espace de noms associé (discussion ou principal).",
+ "apihelp-feedrecentchanges-param-days": "Jours auxquels limiter le résultat.",
+ "apihelp-feedrecentchanges-param-limit": "Nombre maximal de résultats à renvoyer.",
+ "apihelp-feedrecentchanges-param-from": "Afficher les modifications depuis lors.",
+ "apihelp-feedrecentchanges-param-hideminor": "Masquer les modifications mineures.",
+ "apihelp-feedrecentchanges-param-hidebots": "Masquer les modifications faites par des robots.",
+ "apihelp-feedrecentchanges-param-hideanons": "Masquer les modifications faites par des utilisateurs anonymes.",
+ "apihelp-feedrecentchanges-param-hideliu": "Masquer les modifications faites par des utilisateurs enregistrés.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Masquer les modifications contrôlées.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Masquer les modifications faites par l'utilisateur actuel.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filtrer par balise.",
+ "apihelp-feedrecentchanges-param-target": "Afficher uniquement les modifications sur les pages liées depuis cette page.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Afficher les modifications plutôt sur les pages liées vers la page sélectionnée.",
+ "apihelp-feedrecentchanges-example-simple": "Afficher les modifications récentes",
+ "apihelp-feedrecentchanges-example-30days": "Afficher les modifications récentes sur 30 jours",
+ "apihelp-feedwatchlist-description": "Renvoie un flux de liste de suivi.",
+ "apihelp-feedwatchlist-param-feedformat": "Le format du flux.",
+ "apihelp-feedwatchlist-param-hours": "Lister les pages modifiées lors de ce nombre d’heures depuis maintenant.",
+ "apihelp-feedwatchlist-param-linktosections": "Lier directement pour modifier les sections si possible.",
+ "apihelp-feedwatchlist-example-default": "Afficher le flux de la liste de suivi",
+ "apihelp-feedwatchlist-example-all6hrs": "Afficher toutes les modifications sur les pages suivies dans les dernières 6 heures",
+ "apihelp-filerevert-description": "Rétablir un fichier dans une ancienne version.",
+ "apihelp-filerevert-param-filename": "Nom de fichier cible, sans le préfixe File:.",
+ "apihelp-filerevert-param-comment": "Télécharger le commentaire.",
+ "apihelp-filerevert-param-archivename": "Nom d’archive de la révision à rétablir.",
+ "apihelp-filerevert-example-revert": "Rétablir <kbd>Wiki.png</kbd> dans la version du <kbd>2011-03-05T15:27:40Z</kbd>",
+ "apihelp-help-description": "Afficher l’aide pour les modules spécifiés.",
+ "apihelp-help-param-modules": "Modules pour lesquels afficher l’aide (valeurs des paramètres <var>action</var> et <var>format</var>, ou <kbd>main</kbd>). Les sous-modules peuvent être spécifiés avec un <kbd>+</kbd>.",
+ "apihelp-help-param-submodules": "Inclure l’aide pour les sous-modules du module nommé.",
+ "apihelp-help-param-recursivesubmodules": "Inclure l’aide pour les sous-modules de façon récursive.",
+ "apihelp-help-param-helpformat": "Format de sortie de l’aide.",
+ "apihelp-help-param-wrap": "Inclut la sortie dans une structure de réponse API standard.",
+ "apihelp-help-param-toc": "Inclure une table des matières dans la sortir HTML.",
+ "apihelp-help-example-main": "Aide pour le module principal",
+ "apihelp-help-example-recursive": "Toute l’aide sur une page",
+ "apihelp-help-example-help": "Aide pour le module d’aide lui-même",
+ "apihelp-help-example-query": "Aide pour deux sous-modules de recherche",
+ "apihelp-imagerotate-description": "Faire pivoter une ou plusieurs images.",
+ "apihelp-imagerotate-param-rotation": "Degrés de rotation de l’image dans le sens des aiguilles d’une montre.",
+ "apihelp-imagerotate-example-simple": "Faire pivoter <kbd>File:Example.png</kbd> de <kbd>90</kbd> degrés.",
+ "apihelp-imagerotate-example-generator": "Faire pivoter toutes les images de <kbd>Category:Flip</kbd> de <kbd>180</kbd> degrés.",
+ "apihelp-import-description": "Importer une page depuis un autre wiki, ou un fichier XML.\n\nNoter que le POST HTTP doit être effectué comme un import de fichier (c’est-à-dire en utilisant multipart/form-data) lors de l’envoi d’un fichier pour le paramètre <var>xml</var>.",
+ "apihelp-import-param-summary": "Importer le résumé.",
+ "apihelp-import-param-xml": "Fichier XML téléchargé.",
+ "apihelp-import-param-interwikisource": "Pour les importations interwiki : wiki depuis lequel importer.",
+ "apihelp-import-param-interwikipage": "Pour les importations interwiki : page à importer.",
+ "apihelp-import-param-fullhistory": "Pour les importations interwiki : importer tout l’historique, et pas seulement la version courante.",
+ "apihelp-import-param-templates": "Pour les importations interwiki : importer aussi tous les modèles inclus.",
+ "apihelp-import-param-namespace": "Pour les importations interwiki : importer vers cet espace de noms.",
+ "apihelp-import-param-rootpage": "Importer comme une sous-page de cette page.",
+ "apihelp-import-example-import": "Importer [[meta:Help:Parserfunctions]] vers l’espace de noms 100 avec tout l’historique.",
+ "apihelp-login-description": "Se connecter et obtenir les cookies d’authentification.\n\nDans le cas d’une connexion réussie, les cookies nécessaires seront inclus dans les entêtes de la réponse HTTP. Dans le cas d’une connexion en échec, les essais ultérieurs pourront être réduits afin de limiter les attaques automatisées de découverte du mot de passe.",
+ "apihelp-login-param-name": "Nom d’utilisateur.",
+ "apihelp-login-param-password": "Mot de passe.",
+ "apihelp-login-param-domain": "Domaine (facultatif).",
+ "apihelp-login-param-token": "Jeton de connexion obtenu à la première requête.",
+ "apihelp-login-example-gettoken": "Récupérer un jeton de connexion",
+ "apihelp-login-example-login": "Se connecter",
+ "apihelp-logout-description": "Se déconnecter et effacer les données de session.",
+ "apihelp-logout-example-logout": "Déconnecter l’utilisateur actuel.",
+ "apihelp-managetags-description": "Effectuer des tâches de gestion relatives à la modification des balises.",
+ "apihelp-managetags-param-operation": "Quelle opération effectuer :\n;create:Créer une nouvelle balise de modification pour un usage manuel.\n;delete:Supprimer une balise de modification de la base de données, y compris la suppression de la marque de toutes les révisions, entrées de modification récente et entrées de journal dans lesquelles elle serait utilisée.\n;activate:Activer une balise de modification, permettant aux utilisateurs de l’appliquer manuellement.\n;deactivate:Désactiver une balise de modification, empêchant les utilisateurs de l’appliquer manuellement.",
+ "apihelp-managetags-param-tag": "Balise à créer, supprimer, activer ou désactiver. Pour la création de balise, elle ne doit pas exister. Pour la suppression de balise, elle doit exister. Pour l’activation de balise, elle doit exister et ne pas être utilisée par une extension. Pour la désactivation de balise, elle doit être actuellement active et définie manuellement.",
+ "apihelp-managetags-param-reason": "Un motif facultatif pour créer, supprimer, activer ou désactiver la balise.",
+ "apihelp-managetags-param-ignorewarnings": "S’il faut ignorer tout avertissement qui se produirait au cours de l’opération.",
+ "apihelp-managetags-example-create": "Créer une balise nommée <kbd>pourriel</kbd> avec le motif <kbd>À utiliser lors de la revue des modifications</kbd>",
+ "apihelp-managetags-example-delete": "Supprimer la balise <kbd>vandlaisme</kbd> avec le motif <kbd>Mal épelé</kbd>",
+ "apihelp-managetags-example-activate": "Activer une balise nommée <kbd>pourriel</kbd> avec le motif <kbd>À utiliser dans la revue des modifications</kbd>",
+ "apihelp-managetags-example-deactivate": "Désactiver une balise nommée <kbd>pourriel</kbd> avec le motif <kbd>Plus nécessaire</kbd>",
+ "apihelp-move-description": "Déplacer une page.",
+ "apihelp-move-param-from": "Titre de la page à renommer. Impossible de l’utiliser avec <var>$1fromid</var>.",
+ "apihelp-move-param-fromid": "ID de la page à renommer. Impossible à utiliser avec <var>$1from</var>.",
+ "apihelp-move-param-to": "Titre de la page renommée.",
+ "apihelp-move-param-reason": "Motif du renommage.",
+ "apihelp-move-param-movetalk": "Renommer la page de discussion, si elle existe.",
+ "apihelp-move-param-movesubpages": "Renommer les sous-pages, le cas échéant.",
+ "apihelp-move-param-noredirect": "Ne pas créer une redirection.",
+ "apihelp-move-param-watch": "Ajouter une page et la redirection à liste de suivi de l'utilisateur actuel.",
+ "apihelp-move-param-unwatch": "Supprimer la page et la redirection de la liste de suivi de l'utilisateur actuel.",
+ "apihelp-move-param-watchlist": "Ajouter ou supprimer sans condition la page de la liste de suivi de l'utilisateur actuel, utiliser les préférences ou ne pas changer le suivi.",
+ "apihelp-move-param-ignorewarnings": "Ignorer tous les avertissements.",
+ "apihelp-move-example-move": "Déplacer <kbd>Mauvais titre</kbd> en <kbd>Bon titre</kbd> sans garder de redirection.",
+ "apihelp-opensearch-description": "Rechercher dans le wiki en utilisant le protocole OpenSearch.",
+ "apihelp-opensearch-param-search": "Chaîne de recherche.",
+ "apihelp-opensearch-param-limit": "Nombre maximal de résultats à renvoyer.",
+ "apihelp-opensearch-param-namespace": "Espaces de nom à rechercher.",
+ "apihelp-opensearch-param-suggest": "Ne rien faire si <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> vaut faux.",
+ "apihelp-opensearch-param-redirects": "Comment gérer les redirections :\n;return:Renvoie la redirection elle-même.\n;resolve:Renvoie la page cible. Peut renvoyer moins de $1limit résultats.\nPour des raisons historiques, la valeur par défaut est « return » pour $1format=json et « resolve » pour les autres formats.",
+ "apihelp-opensearch-param-format": "Le format de sortie.",
+ "apihelp-opensearch-example-te": "Trouver les pages commençant par <kbd>Te</kbd>.",
+ "apihelp-options-description": "Modifier les préférences de l’utilisateur courant.\n\nSeules les options enregistrées dans le cœur ou dans l’une des extensions installées, ou les options avec une clé préfixée par « userjs- » (devant être utilisées dans les scripts utilisateur), peuvent être définies.",
+ "apihelp-options-param-reset": "Réinitialise les préférences aux valeurs par défaut du site.",
+ "apihelp-options-param-resetkinds": "Liste des types d’option à réinitialiser quand l’option <var>$1reset</var> est définie.",
+ "apihelp-options-param-change": "Liste des modifications, au format nom=valeur (par ex. skin=vector). La valeur ne peut pas contenir de caractère barre verticale. Si aucune valeur n’est fournie (pas même un signe égal), par ex., nomoption|autreoption|…, l’option sera réinitialisée à sa valeur par défaut.",
+ "apihelp-options-param-optionname": "Un nom d’option qui doit être fixé à la valeur fournie par <var>$1optionvalue</var>.",
+ "apihelp-options-param-optionvalue": "La valeur d’une option spécifiée par <var>$1optionname</var> peut contenir des caractères barre verticale.",
+ "apihelp-options-example-reset": "Réinitialiser toutes les préférences",
+ "apihelp-options-example-change": "Modifier les préférences <kbd>skin</kbd> et <kbd>hideminor</kbd>.",
+ "apihelp-options-example-complex": "Réinitialiser toutes les préférences, puis définir <kbd>skin</kbd> et <kbd>nickname</kbd>.",
+ "apihelp-paraminfo-description": "Obtenir des informations sur les modules de l’API.",
+ "apihelp-paraminfo-param-modules": "Liste des noms de module (valeurs des paramètres <var>action</var> et <var>format</var>, ou <kbd>main</kbd>). Peut spécifier des sous-modules avec un <kbd>+</kbd>.",
+ "apihelp-paraminfo-param-helpformat": "Format des chaînes d’aide.",
+ "apihelp-paraminfo-param-querymodules": "Liste des noms de module de requêtage (valeur des paramètres <var>prop</var>, <var>meta</var> ou <var>list</var>=). Utiliser <kbd>$1modules=query+foo</kbd> au lieu de <kbd>$1querymodules=foo</kbd>.",
+ "apihelp-paraminfo-param-mainmodule": "Obtenir aussi des informations sur le module principal (niveau supérieur). Utiliser plutôt <kbd>$1modules=main</kbd>.",
+ "apihelp-paraminfo-param-pagesetmodule": "Obtenir aussi des informations sur le module pageset (en fournissant titles= et ses amis).",
+ "apihelp-paraminfo-param-formatmodules": "Liste des noms de module de mise en forme (valeur du paramètre <var>format</var>). Utiliser plutôt <var>$1modules</var>.",
+ "apihelp-paraminfo-example-1": "Afficher les informations pour <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd> et <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+ "apihelp-parse-description": "Analyse le contenu et renvoie le résultat de l’analyseur.\n\nVoyez les différents modules prop de <kbd>[[Special:ApiHelp/query|action=query]]</kbd> pour avoir de l’information sur la version actuelle d’une page.\n\nIl y a plusieurs moyens de spécifier le texte à analyser :\n# Spécifier une page ou une révision, en utilisant <var>$1page</var>, <var>$1pageid</var> ou <var>$1oldid</var>.\n# Spécifier explicitement un contenu, en utilisant <var>$1text</var>, <var>$1title</var> et <var>$1contentmodel</var>\n# Spécifier uniquement un résumé à analyser. <var>$1prop</var> doit recevoir une valeur vide.",
+ "apihelp-parse-param-title": "Titre de la page à laquelle appartient le texte. Si omis, <var>$1contentmodel</var> doit être spécifié, et [[API]] sera utilisé comme titre.",
+ "apihelp-parse-param-text": "Texte à analyser. utiliser <var>$1title</var> ou <var>$1contentmodel</var> pour contrôler le modèle de contenu.",
+ "apihelp-parse-param-summary": "Résumé à analyser.",
+ "apihelp-parse-param-page": "Analyser le contenu de cette page. Impossible à utiliser avec <var>$1text</var> et <var>$1title</var>.",
+ "apihelp-parse-param-pageid": "Analyser le contenu de cette page. Écrase <var>$1page</var>.",
+ "apihelp-parse-param-redirects": "Si le paramètre <var>$1page</var> ou <var>$1pageid</var> est positionné sur une redirection, la résoudre.",
+ "apihelp-parse-param-oldid": "Analyser le contenu de cette révision. Écrase <var>$1page</var> et <var>$1pageid</var>.",
+ "apihelp-parse-param-prop": "Quelles informations obtenir :\n;text:Fournit le texte analysé du wikitexte.\n;langlinks:Fournit les liens de langue dans le wikitexte analysé.\n;categories:Fournit les catégories dans le wikitexte analysé.\n;categorieshtml:Fournit la version HTML des catégories.\n;links:Fournit les liens internes dans le wikitexte analysé.\n;templates:Fournit les modèles dans le wikitexte analysé.\n;images:Fournit les images dans le wikitexte analysé.\n;externallinks:Fournit les liens externes dans le wikitexte analysé.\n;sections:Fournit les sections dans le wikitexte analysé.\n;revid:Ajoute l’ID de révision de la page analysée.\n;displaytitle:Ajoute le titre du wikitexte analysé.\n;headitems:Fournit les éléments à mettre dans le &lt;head&gt; de la page.\n;headhtml:Fournit le &lt;head&gt; analysé de la page.\n;modules:Fournit les modules ResourceLoader utilisés sur la page.\n;indicators:Fournit le HTML des indicateurs d’état de la page utilisés dans la page.\n;iwlinks:Fournit les liens interwiki dans le wikitexte analysé.\n;wikitext:Fournit le wikitexte d’origine qui a été analysé.\n;properties:Fournit différentes propriétés définies dans le wikitexte analysé.\n;limitreportdata:Fournit le rapport de limite de façon structurée. Ne fournit aucune donnée, quand $1disablepp est activé.\n;limitreporthtml:Fournit la version HTML du rapport de limite. Ne fournit aucune donnée, quand $1disablepp est activé.",
+ "apihelp-parse-param-pst": "Faire une transformation avant enregistrement de l’entrée avant de l’analyser. Valide uniquement quand utilisé avec du texte.",
+ "apihelp-parse-param-onlypst": "Faire une transformation avant enregistrement (PST) de l’entrée, mais ne pas l’analyser. Renvoie le même wikitexte, après que la PST a été appliquée. Valide uniquement quand utilisé avec <var>$1text</var>.",
+ "apihelp-parse-param-effectivelanglinks": "Inclut les liens de langue fournis par les extensions (à utiliser avec <kbd>$1prop=langlinks</kbd>).",
+ "apihelp-parse-param-section": "Récupérer uniquement le contenu de ce numéro de section ou quand <kbd>nouveau</kbd> génère une nouvelle section.\n\nLa <kbd>nouvelle</kbd> section est mise à l’honneur uniquement quand <var>text</var> est spécifié.",
+ "apihelp-parse-param-sectiontitle": "Nouveau titre de section quand <var>section</var> vaut <kbd>nouveau</kbd>.\n\nÀ la différence de la modification de page, cela ne revient pas à <var>summary</var> quand il est omis ou vide.",
+ "apihelp-parse-param-disablepp": "Désactiver le rapport PP de la sortie de l’analyseur.",
+ "apihelp-parse-param-disableeditsection": "Désactiver les liens de modification de section de la sortie de l’analyseur.",
+ "apihelp-parse-param-generatexml": "Générer un arbre d’analyse XML (nécessite le modèle de contenu <code>$1</code>).",
+ "apihelp-parse-param-preview": "Analyser en mode aperçu.",
+ "apihelp-parse-param-sectionpreview": "Analyser en mode aperçu de section (active aussi le mode aperçu).",
+ "apihelp-parse-param-disabletoc": "Désactiver la table des matières dans la sortie.",
+ "apihelp-parse-param-contentformat": "Format de sérialisation du contenu utilisé pour le texte d’entrée. Valide uniquement si utilisé avec $1text.",
+ "apihelp-parse-param-contentmodel": "Modèle de contenu du texte d’entrée. Si omis, $1title doit être spécifié, et la valeur par défaut sera le modèle du titre spécifié. Valide uniquement quand utilisé avec $1text.",
+ "apihelp-parse-example-page": "Analyser une page.",
+ "apihelp-parse-example-text": "Analyser le wikitexte.",
+ "apihelp-parse-example-texttitle": "Analyser du wikitexte, en spécifiant le titre de la page.",
+ "apihelp-parse-example-summary": "Analyser un résumé.",
+ "apihelp-patrol-description": "Patrouiller une page ou une révision.",
+ "apihelp-patrol-param-rcid": "ID de modification récente à patrouiller.",
+ "apihelp-patrol-param-revid": "ID de révision à patrouiller.",
+ "apihelp-patrol-example-rcid": "Patrouiller une modification récente",
+ "apihelp-patrol-example-revid": "Patrouiller une révision",
+ "apihelp-protect-description": "Modifier le niveau de protection d’une page.",
+ "apihelp-protect-param-title": "Titre de la page à (dé)protéger. Impossible à utiliser avec $1pageid.",
+ "apihelp-protect-param-pageid": "ID de la page à (dé)protéger. Impossible à utiliser avec $1title.",
+ "apihelp-protect-param-protections": "Liste des niveaux de protection, au format <kbd>action=niveau</kbd> (par ex. <kbd>edit=sysop</kbd>).\n\n<strong>NOTE :<strong> Toutes les actions non listées auront leur restrictions supprimées.",
+ "apihelp-protect-param-expiry": "Horodatages d’expiration. Si un seul horodatage est fourni, il sera utilisé pour toutes les protections. Utiliser <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd> ou <kbd>never</kbd> pour une protection sans expiration.",
+ "apihelp-protect-param-reason": "Motif de (dé)protection.",
+ "apihelp-protect-param-cascade": "Activer la protection en cascade (c’est-à-dire protéger les pages incluses dans cette page). Ignoré si tous les niveaux de protection fournis ne supportent pas la mise en cascade.",
+ "apihelp-protect-param-watch": "Si activé, ajouter la page (dé)protégée à la liste de suivi de l'utilisateur actuel.",
+ "apihelp-protect-param-watchlist": "Ajouter ou supprimer sans condition la page de la liste de suivi de l'utilisateur actuel, utiliser les préférences ou ne pas modifier le suivi.",
+ "apihelp-protect-example-protect": "Protéger une page",
+ "apihelp-protect-example-unprotect": "Enlever la protection d’une page en mettant les restrictions à <kbd>all</kbd>.",
+ "apihelp-protect-example-unprotect2": "Enlever la protection de la page en ne mettant aucune restriction",
+ "apihelp-purge-description": "Vider le cache des titres fournis.\n\nNécessite une requête POST si l’utilisateur n’est pas connecté.",
+ "apihelp-purge-param-forcelinkupdate": "Mettre à jour les tables de liens.",
+ "apihelp-purge-param-forcerecursivelinkupdate": "Mettre à jour la table des liens, et mettre à jour les tables de liens pour toute page qui utilise cette page comme modèle",
+ "apihelp-purge-example-simple": "Purger les pages <kbd>Page principale</kbd> et <kbd>API</kbd>.",
+ "apihelp-purge-example-generator": "Purger les 10 premières pages de l’espace de noms principal",
+ "apihelp-query-description": "Extraire des données de et sur MédiaWiki.\n\nToutes les modifications de données devront d’abord utiliser une requête pour obtenir un jeton, afin d’éviter les abus de la part de sites malveillants.",
+ "apihelp-query-param-prop": "Quelles propriétés obtenir des pages demandées.",
+ "apihelp-query-param-list": "Quelles listes obtenir.",
+ "apihelp-query-param-meta": "Quelles métadonnées obtenir.",
+ "apihelp-query-param-indexpageids": "Inclure une section pageids supplémentaire listant tous les IDs de page renvoyés.",
+ "apihelp-query-param-export": "Exporter les révisions actuelles de toutes les pages fournies ou générées.",
+ "apihelp-query-param-exportnowrap": "Renvoyer le XML exporté sans l’inclure dans un résultat XML (même format que [[Special:Export]]). Utilisable uniquement avec $1export.",
+ "apihelp-query-param-iwurl": "S’il faut obtenir l’URL complète si le titre est un lien interwiki.",
+ "apihelp-query-param-continue": "Quand il est présent, met en forme query-continue sous forme de paires clé-valeur qui devrait simplement être fusionné dans la requête d’origine. Ce paramètre doit être fixé à une chaîne vide dans la requête initiale.\n\nCe paramètre est recommandé pour tout nouveau développement, et sera mis par défaut dans la prochaine version de l’API.",
+ "apihelp-query-param-rawcontinue": "Actuellement ignoré. Plus tard, <var>$1continue</var> deviendra la valeur par défaut et sera nécessaire pour recevoir les données brutes de <samp>query-continue</samp>.",
+ "apihelp-query-example-revisions": "Récupérer [[Special:ApiHelp/query+siteinfo|l’info du site]] et [[Special:ApiHelp/query+revisions|les révisions]] de <kbd>Page principale</kbd>.",
+ "apihelp-query-example-allpages": "Récupérer les révisions des pages commençant par <kbd>API/</kbd>.",
+ "apihelp-query+allcategories-description": "Énumérer toutes les catégories.",
+ "apihelp-query+allcategories-param-from": "La catégorie depuis laquelle démarrer l’énumération.",
+ "apihelp-query+allcategories-param-to": "La catégorie à laquelle terminer l’énumération.",
+ "apihelp-query+allcategories-param-prefix": "Rechercher tous les titres de catégorie qui commencent avec cette valeur.",
+ "apihelp-query+allcategories-param-dir": "Direction dans laquelle trier.",
+ "apihelp-query+allcategories-param-min": "Renvoyer uniquement les catégories avec au moins ce nombre de membres.",
+ "apihelp-query+allcategories-param-max": "Renvoyer uniquement les catégories avec au plus ce nombre de membres.",
+ "apihelp-query+allcategories-param-limit": "Combien de catégories renvoyer.",
+ "apihelp-query+allcategories-param-prop": "Quelles propriétés récupérer :\n;size:Ajoute le nombre de pages dans la catégorie.\n;hidden:Marque les catégories qui sont cachées avec _&#95;HIDDENCAT_&#95;.",
+ "apihelp-query+allcategories-example-size": "Lister les catégories avec l’information sur le nombre de pages dans chacune",
+ "apihelp-query+allcategories-example-generator": "Récupérer l’information sur la page de catégorie elle-même pour les catégories commençant par <kbd>List</kbd>.",
+ "apihelp-query+alldeletedrevisions-description": "Lister toutes les révisions supprimées par un utilisateur ou dans un espace de noms.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Utilisable uniquement avec <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Impossible à utiliser avec <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-param-start": "L’horodatage auquel démarrer l’énumération.",
+ "apihelp-query+alldeletedrevisions-param-end": "L’horodatage auquel arrêter l’énumération.",
+ "apihelp-query+alldeletedrevisions-param-from": "Démarrer la liste à ce titre.",
+ "apihelp-query+alldeletedrevisions-param-to": "Arrêter la liste à ce titre.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Rechercher tous les titres de page commençant par cette valeur.",
+ "apihelp-query+alldeletedrevisions-param-tag": "Lister uniquement les révisions marquées avec cette balise.",
+ "apihelp-query+alldeletedrevisions-param-user": "Lister uniquement les révisions par cet utilisateur.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Ne pas lister les révisions par cet utilisateur.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Lister uniquement les pages dans cet espace de noms.",
+ "apihelp-query+alldeletedrevisions-param-miser-user-namespace": "<strong>REMARQUE :</strong> Du fait du [[mw:Manual:$wgMiserMode|mode minimal]], utiliser <var>$1user</var> et <var>$1namespace</var> ensemble peut aboutir à moins de résultats renvoyés que <var>$1limit</var> avant de continuer ; dans les cas extrêmes, zéro résultats peuvent être renvoyés.",
+ "apihelp-query+alldeletedrevisions-param-generatetitles": "Utilisé comme générateur, générer des titres plutôt que des IDs de révision.",
+ "apihelp-query+alldeletedrevisions-example-user": "Lister les 50 dernières contributions supprimées par l'utilisateur <kbd>Exemple</kbd>.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Lister les 50 premières révisions supprimées dans l’espace de noms principal.",
+ "apihelp-query+allfileusages-description": "Lister toutes les utilisations de fichier, y compris ceux n’existant pas.",
+ "apihelp-query+allfileusages-param-from": "Le titre du fichier depuis lequel commencer l’énumération.",
+ "apihelp-query+allfileusages-param-to": "Le titre du fichier auquel arrêter l’énumération.",
+ "apihelp-query+allfileusages-param-prefix": "Rechercher tous les fichiers dont le titre commence par cette valeur.",
+ "apihelp-query+allfileusages-param-unique": "Afficher uniquement les titres de fichier distincts. Impossible à utiliser avec $1prop=ids.\nQuand utilisé comme générateur, produit les pages cibles au lieu des sources.",
+ "apihelp-query+allfileusages-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page utilisatrice (impossible à utiliser avec $1unique).\n;title:Ajoute le titre du fichier.",
+ "apihelp-query+allfileusages-param-limit": "Combien d’éléments renvoyer au total.",
+ "apihelp-query+allfileusages-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+allfileusages-example-B": "Lister les titres de fichier, y compris les manquants, avec les IDs de page d’où ils proviennent, en commençant à <kbd>B</kbd>.",
+ "apihelp-query+allfileusages-example-unique": "Lister les titres de fichier uniques",
+ "apihelp-query+allfileusages-example-unique-generator": "Obtient tous les titres de fichier, en marquant les manquants",
+ "apihelp-query+allfileusages-example-generator": "Obtient les pages contenant les fichiers",
+ "apihelp-query+allimages-description": "Énumérer toutes les images séquentiellement.",
+ "apihelp-query+allimages-param-sort": "Propriété par laquelle trier.",
+ "apihelp-query+allimages-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+allimages-param-from": "Le titre de l’image depuis laquelle démarrer l’énumération. Ne peut être utilisé qu’avec $1sort=name.",
+ "apihelp-query+allimages-param-to": "Le titre de l’image auquel arrêter l’énumération. Ne peut être utilisé qu’avec $1sort=name.",
+ "apihelp-query+allimages-param-start": "L’horodatage depuis lequel énumérer. Ne peut être utilisé qu’avec $1sort=timestamp.",
+ "apihelp-query+allimages-param-end": "L’horodatage de fin de l’énumération. Ne peut être utilisé qu’avec $1sort=timestamp.",
+ "apihelp-query+allimages-param-prefix": "Rechercher toutes les images dont le titre commence par cette valeur. Utilisable uniquement avec $1sort=name.",
+ "apihelp-query+allimages-param-minsize": "Restreindre aux images avec au moins ce nombre d’octets.",
+ "apihelp-query+allimages-param-maxsize": "Restreindre aux images avec au plus ce nombre d’octets.",
+ "apihelp-query+allimages-param-sha1": "Hachage SHA1 de l’image. Écrase $1sha1base36.",
+ "apihelp-query+allimages-param-sha1base36": "Hachage SHA1 de l’image en base 36 (utilisé dans MédiaWiki).",
+ "apihelp-query+allimages-param-user": "Renvoyer seulement les fichiers téléchargés par cet utilisateur. Utilisable uniquement avec $1sort=timestamp. Impossible à utiliser avec $1filterbots.",
+ "apihelp-query+allimages-param-filterbots": "Comment filtrer les fichiers téléchargés par des robots. Peut être utilisé uniquement avec $1sort=timestamp. Impossible à utiliser avec $1user.",
+ "apihelp-query+allimages-param-mime": "Quels types MIME rechercher, par ex. <kbd>image/jpeg</kbd>.",
+ "apihelp-query+allimages-param-limit": "Combien d’images renvoyer au total.",
+ "apihelp-query+allimages-example-B": "Afficher une liste des fichiers commençant par la lettre <kbd>B</kbd>.",
+ "apihelp-query+allimages-example-recent": "Afficher une liste des fichiers récemment téléchargés semblable à [[Special:NewFiles]]",
+ "apihelp-query+allimages-example-mimetypes": "Afficher une liste de fichiers avec le type MIME <kbd>image/png</kbd> ou <kbd>image/gif</kbd>",
+ "apihelp-query+allimages-example-generator": "Afficher l’information sur 4 fichiers commençant par la lettre <kbd>T</kbd>.",
+ "apihelp-query+alllinks-description": "Énumérer tous les liens pointant vers un espace de noms donné.",
+ "apihelp-query+alllinks-param-from": "Le titre du lien auquel démarrer l’énumération.",
+ "apihelp-query+alllinks-param-to": "Le titre du lien auquel arrêter l’énumération.",
+ "apihelp-query+alllinks-param-prefix": "Rechercher tous les titres liés commençant par cette valeur.",
+ "apihelp-query+alllinks-param-unique": "Afficher uniquement les titres liés distincts. Impossible à utiliser avec <kbd>$1prop=ids</kbd>.\nUtilisé avec un générateur, produit les pages cible au lieu des pages source.",
+ "apihelp-query+alllinks-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page de liaison (impossible à utiliser avec <var>$1unique</var>).\n;title:Ajoute le titre du lien.",
+ "apihelp-query+alllinks-param-namespace": "L’espace de noms à énumérer.",
+ "apihelp-query+alllinks-param-limit": "Combien d’éléments renvoyer au total.",
+ "apihelp-query+alllinks-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+alllinks-example-B": "Lister les titres liés, y compris les manquants, avec les IDs des pages d’où ils proviennent, en démarrant à <kbd>B</kbd>.",
+ "apihelp-query+alllinks-example-unique": "Lister les titres liés uniques",
+ "apihelp-query+alllinks-example-unique-generator": "Obtient tous les titres liés, en marquant les manquants",
+ "apihelp-query+alllinks-example-generator": "Obtient les pages contenant les liens",
+ "apihelp-query+allmessages-description": "Renvoyer les messages depuis ce site.",
+ "apihelp-query+allmessages-param-messages": "Quels messages sortir. <kbd>*</kbd> (par défaut) signifie tous les messages.",
+ "apihelp-query+allmessages-param-prop": "Quelles propriétés obtenir.",
+ "apihelp-query+allmessages-param-enableparser": "Si positionné pour activer l’analyseur, traitera en avance le wikitexte du message (substitution des mots magiques, gestion des modèles, etc.).",
+ "apihelp-query+allmessages-param-nocontent": "Si positionné, ne pas inclure le contenu des messages dans la sortie.",
+ "apihelp-query+allmessages-param-includelocal": "Inclure aussi les messages locaux, c’est-à-dire les messages qui n’existent pas dans le logiciel mais sous forme d’une page MediaWiki:.\nCela liste toutes les pages MediaWiki:, donc aussi celles qui ne sont pas vraiment des messages, telles que [[MediaWiki:Common.js|Common.js]].",
+ "apihelp-query+allmessages-param-args": "Arguments à substituer dans le message.",
+ "apihelp-query+allmessages-param-filter": "Renvoyer uniquement les messages avec des noms contenant cette chaîne.",
+ "apihelp-query+allmessages-param-customised": "Renvoyer uniquement les messages dans cet état de personnalisation.",
+ "apihelp-query+allmessages-param-lang": "Renvoyer les messages dans cette langue.",
+ "apihelp-query+allmessages-param-from": "Renvoyer les messages commençant à ce message.",
+ "apihelp-query+allmessages-param-to": "Renvoyer les messages en terminant à ce message.",
+ "apihelp-query+allmessages-param-title": "Nom de page à utiliser comme contexte en analysant le message (pour l’option $1enableparser).",
+ "apihelp-query+allmessages-param-prefix": "Renvoyer les messages avec ce préfixe.",
+ "apihelp-query+allmessages-example-ipb": "Afficher les messages commençant par <kbd>ipb-</kbd>.",
+ "apihelp-query+allmessages-example-de": "Afficher les messages <kbd>august</kbd> et <kbd>mainpage</kbd> en allemand.",
+ "apihelp-query+allpages-description": "Énumérer toutes les pages séquentiellement dans un espace de noms donné.",
+ "apihelp-query+allpages-param-from": "Le titre de la page depuis lequel commencer l’énumération.",
+ "apihelp-query+allpages-param-to": "Le titre de la page auquel stopper l’énumération.",
+ "apihelp-query+allpages-param-prefix": "Rechercher tous les titres de page qui commencent par cette valeur.",
+ "apihelp-query+allpages-param-namespace": "L’espace de noms à énumérer.",
+ "apihelp-query+allpages-param-filterredir": "Quelles pages lister.",
+ "apihelp-query+allpages-param-minsize": "Limiter aux pages avec au moins ce nombre d’octets.",
+ "apihelp-query+allpages-param-maxsize": "Limiter aux pages avec au plus ce nombre d’octets.",
+ "apihelp-query+allpages-param-prtype": "Limiter aux pages protégées uniquement.",
+ "apihelp-query+allpages-param-prlevel": "Filtrer les protections basées sur le niveau de protection (doit être utilisé avec le paramètre $1prtype=).",
+ "apihelp-query+allpages-param-prfiltercascade": "Filtrer les protections d’après leur cascade (ignoré si $1prtype n’est pas positionné).",
+ "apihelp-query+allpages-param-limit": "Combien de pages renvoyer au total.",
+ "apihelp-query+allpages-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+allpages-param-filterlanglinks": "Filtrer si une page a des liens de langue. Noter que cela ne prend pas en compte les liens de langue ajoutés par des extensions.",
+ "apihelp-query+allpages-param-prexpiry": "Quelle expiration de protection sur laquelle filtrer la page :\n;indefinite:N’obtenir que les pages avec une expiration de protection infinie.\n;definite:N’obtenir que les pages avec une expiration de protection définie (spécifique).\n;all:Obtenir toutes les pages avec une expiration de protection.",
+ "apihelp-query+allpages-example-B": "Afficher une liste des pages commençant par la lettre <kbd>B</kbd>.",
+ "apihelp-query+allpages-example-generator": "Afficher l’information sur 4 pages commençant par la lettre <kbd>T</kbd>.",
+ "apihelp-query+allpages-example-generator-revisions": "Afficher le contenu des 2 premières pages hors redirections commençant par <kbd>Re</kbd>.",
+ "apihelp-query+allredirects-description": "Lister toutes les redirections vers un espace de noms.",
+ "apihelp-query+allredirects-param-from": "Le titre de la redirection auquel démarrer l’énumération.",
+ "apihelp-query+allredirects-param-to": "Le titre de la redirection auquel arrêter l’énumération.",
+ "apihelp-query+allredirects-param-prefix": "Rechercher toutes les pages cible commençant par cette valeur.",
+ "apihelp-query+allredirects-param-unique": "Afficher uniquement les pages cibles distinctes. Impossible à utiliser avec $1prop=ids|fragment|interwiki.\nUtilisé avec un générateur, produit les pages cible au lieu des pages source.",
+ "apihelp-query+allredirects-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page de redirection (impossible à utiliser avec <var>$1unique</var>).\n;title:Ajoute le titre de la redirection.\n;fragment:Ajoute le fragment de la redirection, s’il y en a un (impossible à utiliser avec <var>$1unique</var>).\n;interwiki:Ajoute le préfixe interwiki de la redirection, s’il y en a un (impossible à utiliser avec <var>$1unique</var>).",
+ "apihelp-query+allredirects-param-namespace": "L’espace de noms à énumérer.",
+ "apihelp-query+allredirects-param-limit": "Combien d’éléments renvoyer au total.",
+ "apihelp-query+allredirects-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+allredirects-example-B": "Lister les pages cible, y compris les manquantes, avec les IDs de page d’où ils proviennent, en commençant à <kbd>B</kbd>.",
+ "apihelp-query+allredirects-example-unique": "Lister les pages cible unique",
+ "apihelp-query+allredirects-example-unique-generator": "Obtient toutes les pages cible, en marquant les manquantes",
+ "apihelp-query+allredirects-example-generator": "Obtient les pages contenant les redirections",
+ "apihelp-query+alltransclusions-description": "Lister toutes les transclusions (pages intégrées en utilisant &#123;&#123;x&#125;&#125;), y compris les inexistantes.",
+ "apihelp-query+alltransclusions-param-from": "Le titre de la transclusion depuis lequel commencer l’énumération.",
+ "apihelp-query+alltransclusions-param-to": "Le titre de la transclusion auquel arrêter l’énumération.",
+ "apihelp-query+alltransclusions-param-prefix": "Rechercher tous les titres inclus qui commencent par cette valeur.",
+ "apihelp-query+alltransclusions-param-unique": "Afficher uniquement les titres inclus. Impossible à utiliser avec $1prop=ids.\nUtilisé avec un générateur, produit les pages cible plutôt que les pages source.",
+ "apihelp-query+alltransclusions-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page incluse (impossible à utiliser avec $1unique).\n;title:Ajoute le titre de la transclusion.",
+ "apihelp-query+alltransclusions-param-namespace": "L’espace de noms à énumérer.",
+ "apihelp-query+alltransclusions-param-limit": "Combien d’éléments renvoyer au total.",
+ "apihelp-query+alltransclusions-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+alltransclusions-example-B": "Lister les titres inclus, y compris les manquants, avec les IDs des pages d’où ils viennent, en commençant à <kbd>B</kbd>.",
+ "apihelp-query+alltransclusions-example-unique": "Lister les titres inclus uniques",
+ "apihelp-query+alltransclusions-example-unique-generator": "Obtient tous les titres inclus, en marquant les manquants",
+ "apihelp-query+alltransclusions-example-generator": "Obtient les pages contenant des transclusions",
+ "apihelp-query+allusers-description": "Énumérer tous les utilisateurs enregistrés.",
+ "apihelp-query+allusers-param-from": "Le nom d’utilisateur auquel démarrer l’énumération.",
+ "apihelp-query+allusers-param-to": "Le nom d’utilisateur auquel stopper l’énumération.",
+ "apihelp-query+allusers-param-prefix": "Rechercher tous les utilisateurs commençant par cette valeur.",
+ "apihelp-query+allusers-param-dir": "Direction du tri.",
+ "apihelp-query+allusers-param-group": "Inclure uniquement les utilisateurs dans les groupes donnés.",
+ "apihelp-query+allusers-param-excludegroup": "Exclure les utilisateurs dans les groupes donnés.",
+ "apihelp-query+allusers-param-rights": "Inclure uniquement les utilisateurs avec les droits indiqués. Ne comprend pas les droits accordés par des groupes implicites ou auto-promus comme *, user ou autoconfirmed.",
+ "apihelp-query+allusers-param-prop": "Quelles informations inclure :\n;blockinfo:Ajoute l’information sur le bloc actuel d’un utilisateur.\n;groups:Liste des groupes auxquels appartient l’utilisateur. Cela utilise beaucoup de ressources du serveur et peut renvoyer moins de résultats que la limite.\n;implicitgroups:Liste tous les groupes auxquels l’utilisateur est affecté automatiquement.\n;rights:Liste les droits qu’à l’utilisateur.\n;editcount:Ajoute le compteur de modifications de l’utilisateur.\n;registration:Ajoute l’horodatage de l’inscription de l’utilisateur, s’il est disponible (peut être vide).",
+ "apihelp-query+allusers-param-limit": "Combien de noms d’utilisateur renvoyer au total.",
+ "apihelp-query+allusers-param-witheditsonly": "Ne lister que les utilisateurs qui ont fait des modifications.",
+ "apihelp-query+allusers-param-activeusers": "Lister uniquement les utilisateurs actifs durant {{PLURAL:$1|le dernier jour|les $1 derniers jours}}.",
+ "apihelp-query+allusers-example-Y": "Lister les utilisateurs en commençant à <kbd>Y</kbd>.",
+ "apihelp-query+backlinks-description": "Trouver toutes les pages qui ont un lien vers la page donnée.",
+ "apihelp-query+backlinks-param-title": "Titre à rechercher. Impossible à utiliser avec <var>$1pageid</var>.",
+ "apihelp-query+backlinks-param-pageid": "ID de la page à chercher. Impossible à utiliser avec <var>$1title</var>.",
+ "apihelp-query+backlinks-param-namespace": "L’espace de noms à énumérer.",
+ "apihelp-query+backlinks-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+backlinks-param-filterredir": "Comment filtrer les redirections. Si positionné à <kbd>nonredirects</kbd> quand <var>$1redirect</var> est activé, cela ne s’applique qu’au second niveau.",
+ "apihelp-query+backlinks-param-limit": "Combien de pages renvoyer au total. Si $1redirect est activé, la limite s’applique à chaque niveau séparément (ce qui signifie jusqu’à 2 * limite résultats peut être retourné).",
+ "apihelp-query+backlinks-param-redirect": "Si le lien vers une page est une redirection, trouver toutes les pages qui ont un lien vers cette redirection aussi. La limite maximale est divisée par deux.",
+ "apihelp-query+backlinks-example-simple": "Afficher les liens vers <kbd>Main page<kbd>.",
+ "apihelp-query+backlinks-example-generator": "Obtenir des informations sur les pages ayant un lien vers <kbd>Main page<kbd>.",
+ "apihelp-query+blocks-description": "Lister tous les utilisateurs et les adresses IP bloqués.",
+ "apihelp-query+blocks-param-start": "L’horodatage auquel démarrer l’énumération.",
+ "apihelp-query+blocks-param-end": "L’horodatage auquel arrêter l’énumération.",
+ "apihelp-query+blocks-param-ids": "Liste des IDs de bloc à lister (facultatif).",
+ "apihelp-query+blocks-param-users": "Liste des utilisateurs à rechercher (facultatif).",
+ "apihelp-query+blocks-param-ip": "Obtenir tous les blocs s’appliquant à cette adresse IP ou à cette plage CIDR, y compris les blocs de plage.\nImpossible à utiliser avec <var>$3users</var>. Les plages CIDR plus larges que IPv4/$1 ou IPv6/$2 ne sont pas acceptées.",
+ "apihelp-query+blocks-param-limit": "Le nombre maximal de blocs à lister.",
+ "apihelp-query+blocks-param-prop": "Quelles propriétés obtenir :\n;id:Ajoute l’ID du blocage.\n;user:Ajoute le nom de l’utilisateur bloqué.\n;userid:Ajoute l’ID de l’utilisateur bloqué.\n;by:Ajoute le nom de l’utilisateur ayant bloqué.\n;byid:Ajoute l’ID de l’utilisateur ayant bloqué.\n;timestamp:Ajoute l’horodatage du blocage.\n;expiry:Ajoute l’horodatage d’expiration du blocage.\n;reason:Ajoute le motif du blocage.\n;range:Ajoute la plage d’adresses IP affectée par le blocage.\n;flags:Marque le bannissement avec (autoblock, anononly, etc.).",
+ "apihelp-query+blocks-param-show": "Afficher uniquement les éléments correspondant à ces critères.\nPar exemple, pour voir uniquement les blocages infinis sur les adresses IP, mettre <kbd>$1show=ip|!temp</kbd>.",
+ "apihelp-query+blocks-example-simple": "Lister les blocages",
+ "apihelp-query+blocks-example-users": "Lister les blocages des utilisateurs <kbd>Alice</kbd> et <kbd>Bob</kbd>.",
+ "apihelp-query+categories-description": "Lister toutes les catégories auxquelles les pages appartiennent.",
+ "apihelp-query+categories-param-prop": "Quelles propriétés supplémentaires obtenir de chaque catégorie :\n;sortkey:Ajoute la clé de tri (chaîne hexadécimale) et son préfixe (partie lisible) de la catégorie.\n;timestamp:Ajoute l’horodatage de l’ajout de la catégorie.\n;hidden:Marque les catégories cachées avec _&#95;HIDDENCAT_&#95;.",
+ "apihelp-query+categories-param-show": "Quelle sorte de catégories afficher.",
+ "apihelp-query+categories-param-limit": "Combien de catégories renvoyer.",
+ "apihelp-query+categories-param-categories": "Lister uniquement ces catégories. Utile pour vérifier si une certaine page est dans une certaine catégorie.",
+ "apihelp-query+categories-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+categories-example-simple": "Obtenir une liste des catégories auxquelles appartient la page <kbd>Albert Einstein</kbd>.",
+ "apihelp-query+categories-example-generator": "Obtenir des informations sur toutes les catégories utilisées dans la page <kbd>Albert Einstein</kbd>.",
+ "apihelp-query+categoryinfo-description": "Renvoie les informations sur les catégories données.",
+ "apihelp-query+categoryinfo-example-simple": "Obtenir des informations sur <kbd>Category:Foo</kbd> et <kbd>Category:Bar</kbd>.",
+ "apihelp-query+categorymembers-description": "Lister toutes les pages d’une catégorie donnée.",
+ "apihelp-query+categorymembers-param-title": "Quelle catégorie énumérer (obligatoire). Doit comprendre le préfixe <kbd>{{ns:category}}:</kbd>. Impossible à utiliser avec <var>$1pageid</var>.",
+ "apihelp-query+categorymembers-param-pageid": "ID de la page de la catégorie à énumérer. Impossible à utiliser avec <var>$1title</var>.",
+ "apihelp-query+categorymembers-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page.\n;title:Ajoute le titre et l’ID de l’espace de noms de la page.\n;sortkey:Ajoute la clé de tri utilisée pour trier dans la catégorie (chaîne hexadécimale).\n;sortkeyprefix:Ajoute le préfixe de la clé de tri utilisé pour trier dans la catégorie (partie lisible de la clé de tri).\n;type:Ajoute le type dans lequel a été catégorisée la page (page, sous-catégorie ou fichier).\n;timestamp:Ajoute l’horodatage de l’inclusion de la page.",
+ "apihelp-query+categorymembers-param-namespace": "Inclure uniquement les pages dans ces espaces de nom. Remarquez que <kbd>$1type=subcat</kbd> ou <kbd>$1type=file</kbd> peuvent être utilisés à la place de <kbd>$1namespace=14</kbd> ou <kbd>6</kbd>.",
+ "apihelp-query+categorymembers-param-type": "Quel type de membres de la catégorie inclure. Ignoré quand <kbd>$1sort=timestamp</kbd> est positionné.",
+ "apihelp-query+categorymembers-param-limit": "Le nombre maximal de pages à renvoyer.",
+ "apihelp-query+categorymembers-param-sort": "Propriété par laquelle trier.",
+ "apihelp-query+categorymembers-param-dir": "Dans quelle direction trier.",
+ "apihelp-query+categorymembers-param-start": "Horodatage auquel démarrer la liste. Peut être utilisé uniquement avec <kbd>$1sort=timestamp</kbd>.",
+ "apihelp-query+categorymembers-param-end": "Horodatage auquel terminer la liste. Peut être utilisé uniquement avec <kbd>$1sort=timestamp</kbd>.",
+ "apihelp-query+categorymembers-param-starthexsortkey": "Clé de tri à laquelle démarrer le listage, telle que renvoyée par <kbd>$1prop=sortkey</kbd>. Utilisable uniquement avec <kbd>$1sort=sortkey</kbd>.",
+ "apihelp-query+categorymembers-param-endhexsortkey": "Clé de tri à laquelle arrêter le listage, telle que renvoyée par <kbd>$1prop=sortkey</kbd>. Utilisable uniquement avec <kbd>$1sort=sortkey</kbd>.",
+ "apihelp-query+categorymembers-param-startsortkeyprefix": "Préfixe de la clé de tri à laquelle démarrer le listage. Utilisable uniquement avec <kbd>$1sort=sortkey</kbd>. Écrase <var>$1starthexsortkey</var>.",
+ "apihelp-query+categorymembers-param-endsortkeyprefix": "Préfixe de la clé de tri AVANT laquelle se termine le listage (et non pas à, si cette valeur existe elle ne sera pas incluse !). Utilisable uniquement avec $1sort=sortkey. Écrase $1endhexsortkey.",
+ "apihelp-query+categorymembers-param-startsortkey": "Utiliser plutôt $1starthexsortkey.",
+ "apihelp-query+categorymembers-param-endsortkey": "Utiliser plutôt $1endhexsortkey.",
+ "apihelp-query+categorymembers-example-simple": "Obtenir les 10 premières pages de <kbd>Category:Physics</kbd>.",
+ "apihelp-query+categorymembers-example-generator": "Obtenir l’information sur les 10 premières pages de <kbd>Category:Physics</kbd>.",
+ "apihelp-query+contributors-description": "Obtenir la liste des contributeurs connectés et le nombre de contributeurs anonymes d’une page.",
+ "apihelp-query+contributors-param-group": "Inclure uniquement les utilisateurs dans les groupes donnés. Ne pas inclure les groupes implicites ou auto-promus comme *, user ou autoconfirmed.",
+ "apihelp-query+contributors-param-excludegroup": "Exclure les utilisateurs des groupes donnés. Ne pas inclure les groupes implicites ou auto-promus comme *, user ou autoconfirmed.",
+ "apihelp-query+contributors-param-rights": "Inclure uniquement les utilisateurs ayant les droits donnés. Ne pas inclure les droits accordés par les groupes implicites ou auto-promus comme *, user ou autoconfirmed.",
+ "apihelp-query+contributors-param-excluderights": "Exclure les utilisateurs ayant les droits donnés. Ne pas inclure les droits accordés par les groupes implicites ou auto-promus comme *, user ou autoconfirmed.",
+ "apihelp-query+contributors-param-limit": "Combien de contributeurs renvoyer.",
+ "apihelp-query+contributors-example-simple": "Afficher les contributeurs dans la <kbd>Main Page</kbd>.",
+ "apihelp-query+deletedrevisions-description": "Obtenir des informations sur la révision supprimée.\n\nPeut être utilisé de différentes manières :\n# Obtenir les révisions supprimées pour un ensemble de pages, en donnant les titres ou les ids de page. Ordonné par titre et horodatage.\n# Obtenir des données sur un ensemble de révisions supprimées en donnant leurs IDs et leurs ids de révision. Ordonné par ID de révision.",
+ "apihelp-query+deletedrevisions-param-start": "L’horodatage auquel démarrer l’énumération. Ignoré lors du traitement d’une liste d’IDs de révisions.",
+ "apihelp-query+deletedrevisions-param-end": "L’horodatage auquel arrêter l’énumération. Ignoré lors du traitement d’une liste d’IDs de révisions.",
+ "apihelp-query+deletedrevisions-param-tag": "Lister uniquement les révisions marquées par cette balise.",
+ "apihelp-query+deletedrevisions-param-user": "Lister uniquement les révisions faites par cet utilisateur.",
+ "apihelp-query+deletedrevisions-param-excludeuser": "Ne pas lister les révisions faites par cet utilisateur.",
+ "apihelp-query+deletedrevisions-param-limit": "Le nombre maximal de révisions à lister.",
+ "apihelp-query+deletedrevisions-param-prop": "Quelles propriétés obtenir :\n;revid:Ajoute l’ID de la révision supprimée.\n;parentid:Ajoute l’ID de la révision précédente de la page.\n;user:Ajoute l’utilisateur ayant fait la révision.\n;userid:Ajoute l’ID de l’utilisateur ayant fait la révision.\n;comment:Ajoute le commentaire de la révision.\n;parsedcomment:Ajoute le commentaire analysé de la révision.\n;minor:Marque si une révision est mineure.\n;len:Ajoute la taille (en octets) de la révision.\n;sha1:Ajoute le SHA-1 (base 16) de la révision.\n;content:Ajoute le contenu de la révision.\n;tags:Balises pour la révision.",
+ "apihelp-query+deletedrevisions-example-titles": "Lister les révisions supprimées des pages <kbd>Main Page</kbd> et <kbd>Talk:Main Page</kbd>, avec leur contenu.",
+ "apihelp-query+deletedrevisions-example-revids": "Lister les informations pour la révision supprimée <kbd>123456</kbd>.",
+ "apihelp-query+deletedrevs-description": "Lister les révisions supprimées.\n\nOpère selon trois modes :\n# Lister les révisions supprimées pour les titres donnés, triées par horodatage.\n# Lister les contributions supprimées pour l’utilisateur donné, triées par horodatage (pas de titres spécifiés).\n# Lister toutes les révisions supprimées dans l’espace de noms donné, triées par titre et horodatage (aucun titre spécifié, $1user non positionné).\n\nCertains paramètres ne s’appliquent qu’à certains modes et sont ignorés dans les autres.",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Mode|Modes}} : $2",
+ "apihelp-query+deletedrevs-param-start": "L’horodatage auquel démarrer l’énumération.",
+ "apihelp-query+deletedrevs-param-end": "L’horodatage auquel arrêter l’énumération.",
+ "apihelp-query+deletedrevs-param-from": "Démarrer la liste à ce titre.",
+ "apihelp-query+deletedrevs-param-to": "Arrêter la liste à ce titre.",
+ "apihelp-query+deletedrevs-param-prefix": "Rechercher tous les titres de page commençant par cette valeur.",
+ "apihelp-query+deletedrevs-param-unique": "Lister uniquement une révision pour chaque page.",
+ "apihelp-query+deletedrevs-param-tag": "Lister uniquement les révisions marquées par cette balise.",
+ "apihelp-query+deletedrevs-param-user": "Lister uniquement les révisions par cet utilisateur.",
+ "apihelp-query+deletedrevs-param-excludeuser": "Ne pas lister les révisions par cet utilisateur.",
+ "apihelp-query+deletedrevs-param-namespace": "Lister uniquement les pages dans cet espace de noms.",
+ "apihelp-query+deletedrevs-param-limit": "Le nombre maximal de révisions à lister.",
+ "apihelp-query+deletedrevs-param-prop": "Quelles propriétés obtenir :\n;revid:Ajoute l’ID de la révision supprimée.\n;parentid:Ajoute l’ID de la révision précédente de la page.\n;user:Ajoute l’utilisateur ayant fait la révision.\n;userid:Ajoute l’ID de l’utilisateur qui a fait la révision.\n;comment:Ajoute le commentaire de la révision.\n;parsedcomment:Ajoute le commentaire analysé de la révision.\n;minor:Marque si la révision est mineure.\n;len:Ajoute la longueur (en octets) de la révision.\n;sha1:Ajoute le SHA-1 (base 16) de la révision.\n;content:Ajoute le contenu de la révision.\n;token:<span class=\"apihelp-deprecated\">Obsolète.</span> Fournit le jeton de modification.\n;tags:Balises pour la révision.",
+ "apihelp-query+deletedrevs-example-mode1": "Lister les dernières révisions supprimées de des pages <kbd>Main Page</kbd> et <kbd>Talk:Main Page</kbd>, avec le contenu (mode 1).",
+ "apihelp-query+deletedrevs-example-mode2": "Lister les 50 dernières contributions de <kbd>Bob</kbd> supprimées (mode 2).",
+ "apihelp-query+deletedrevs-example-mode3-main": "Lister les 50 premières révisions supprimées dans l’espace de noms principal (mode 3)",
+ "apihelp-query+deletedrevs-example-mode3-talk": "Lister les 50 premières pages supprimées dans l’espace de noms {{ns:talk}} (mode 3).",
+ "apihelp-query+disabled-description": "Ce module de requête a été désactivé.",
+ "apihelp-query+duplicatefiles-description": "Lister tous les fichiers qui sont des doublons des fichiers donnés d’après leurs valeurs de hachage.",
+ "apihelp-query+duplicatefiles-param-limit": "Combien de fichiers dupliqués à renvoyer.",
+ "apihelp-query+duplicatefiles-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+duplicatefiles-param-localonly": "Rechercher les fichiers uniquement dans le référentiel local.",
+ "apihelp-query+duplicatefiles-example-simple": "Rechercher les doublons de [[:File:Albert Einstein Head.jpg]]",
+ "apihelp-query+duplicatefiles-example-generated": "Rechercher les doublons de tous les fichiers",
+ "apihelp-query+embeddedin-description": "Trouver toutes les pages qui incluent (par transclusion) le titre donné.",
+ "apihelp-query+embeddedin-param-title": "Titre à rechercher. Impossible à utiliser avec $1pageid.",
+ "apihelp-query+embeddedin-param-pageid": "ID de la page à rechercher. Impossible à utiliser avec $1title.",
+ "apihelp-query+embeddedin-param-namespace": "L’espace de noms à énumérer.",
+ "apihelp-query+embeddedin-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+embeddedin-param-filterredir": "Comment filtrer les redirections.",
+ "apihelp-query+embeddedin-param-limit": "Combien de pages renvoyer au total.",
+ "apihelp-query+embeddedin-example-simple": "Afficher les pages incluant <kbd>Template:Stub</kbd>.",
+ "apihelp-query+embeddedin-example-generator": "Obteir des informations sur les pages incluant <kbd>Template:Stub</kbd>.",
+ "apihelp-query+extlinks-description": "Renvoyer toutes les URLs externes (non interwikis) des pages données.",
+ "apihelp-query+extlinks-param-limit": "Combien de liens renvoyer.",
+ "apihelp-query+extlinks-param-protocol": "Protocole de l’URL. Si vide et <var>$1query</var> est positionné, le protocole est <kbd>http</kbd>. Laisser à la fois ceci et <var>$1query</var> vide pour lister tous les liens externes.",
+ "apihelp-query+extlinks-param-query": "Rechercher une chaîne sans protocole. Utile pour vérifier si une certaine page contient une certaine URL externe.",
+ "apihelp-query+extlinks-param-expandurl": "Étendre les URLs relatives au protocole avec le protocole canonique.",
+ "apihelp-query+extlinks-example-simple": "Obtenir une liste des liens externes de <kbd>Main Page<kbd>.",
+ "apihelp-query+exturlusage-description": "Énumérer les pages contenant une URL donnée.",
+ "apihelp-query+exturlusage-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page.\n;title:Ajoute le titre et l’ID de l’espace de noms de la page.\n;url:Ajoute l’URL utilisée dans la page.",
+ "apihelp-query+exturlusage-param-protocol": "Protocole de l’URL. Si vide et que <var>$1query</var> est rempli, le protocole est <kbd>http</kbd>. Le laisser avec <var>$1query</var> vide pour lister tous les liens externes.",
+ "apihelp-query+exturlusage-param-query": "Rechercher une chaîne sans protocole. Voyez [[Special:LinkSearch]]. Le laisser vide liste tous les liens externes.",
+ "apihelp-query+exturlusage-param-namespace": "Les espaces de nom à énumérer.",
+ "apihelp-query+exturlusage-param-limit": "Combien de pages renvoyer.",
+ "apihelp-query+exturlusage-param-expandurl": "Étendre les URLs relatives au protocole avec le protocole canonique.",
+ "apihelp-query+exturlusage-example-simple": "Afficher les pages avec un lien vers <kbd>http://www.mediawiki.org</kbd>.",
+ "apihelp-query+filearchive-description": "Énumérer séquentiellement tous les fichiers supprimés.",
+ "apihelp-query+filearchive-param-from": "Le titre de l’image auquel démarrer l’énumération.",
+ "apihelp-query+filearchive-param-to": "Le titre de l’image auquel arrêter l’énumération.",
+ "apihelp-query+filearchive-param-prefix": "Rechercher tous les titres d’image qui commencent par cette valeur.",
+ "apihelp-query+filearchive-param-limit": "Combien d’images renvoyer au total.",
+ "apihelp-query+filearchive-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+filearchive-param-sha1": "Hachage SHA1 de l’image. Écrase $1sha1base36.",
+ "apihelp-query+filearchive-param-sha1base36": "Hachage SHA1 de l’image en base 36 (utilisé dans MédiaWiki).",
+ "apihelp-query+filearchive-param-prop": "Quelle information obtenir sur l’image :\n;sha1:Ajoute le hachage SHA-1 pour l’image.\n;timestamp:Ajoute l÷’horodatage pour la version téléchargée.\n;user:Ajoute l’utilisateur qui a téléchargé la version de l’image.\n;size:Ajoute la taille de l’image en octets et la hauteur, la largeur et le nombre de page (si c’est applicable).\n;dimensions:Alias pour la taille.\n;description:Ajoute la description de la version de l’image.\n;parseddescription:Analyser la description de la version.\n;mime:Ajoute le MIME de l’image.\n;mediatype:Ajoute le type de média de l’image.\n;metadata:Liste les métadonnées Exif pour la version de l’image.\n;bitdepth:Ajoute la profondeur de bit de la version.\n;archivename:Ajoute le nom de fichier de la version d’archive pour les versions autres que la dernière.",
+ "apihelp-query+filearchive-example-simple": "Afficher une liste de tous les fichiers supprimés",
+ "apihelp-query+filerepoinfo-description": "Renvoyer les méta-informations sur les référentiels d’image configurés dans le wiki.",
+ "apihelp-query+filerepoinfo-param-prop": "Quelles propriétés du référentiel récupérer (il peut y en avoir plus de disponibles sur certains wikis) :\n;apiurl:URL de l’API du référentiel - utile pour obtenir les infos de l’image depuis l’hôte.\n;name:La clé du référentiel - utilisé par ex. dans les valeurs de retour de <var>[[mw:Manual:$wgForeignFileRepos|$wgForeignFileRepos]]</var> et [[Special:ApiHelp/query+imageinfo|imageinfo]].\n;displayname:Le nom lisible du wiki référentiel.\n;rooturl:URL racine des chemins d’image.\n;local:Si ce référentiel est le référentiel local ou non.",
+ "apihelp-query+filerepoinfo-example-simple": "Obtenir l’information sur les référentiels de fichier",
+ "apihelp-query+fileusage-description": "Trouver toutes les pages qui utilisent les fichiers donnés.",
+ "apihelp-query+fileusage-param-prop": "Quelles propriétés obtenir :\n;pageid:ID de chaque page.\n;title:Titre de chaque page.\n;redirect:Marque si la page est une redirection.",
+ "apihelp-query+fileusage-param-namespace": "Inclure uniquement les pages dans ces espaces de nom.",
+ "apihelp-query+fileusage-param-limit": "Combien renvoyer.",
+ "apihelp-query+fileusage-param-show": "Afficher uniquement les éléments qui correspondent à ces critères :\n;redirect:Afficher uniquement les redirections.\n;!redirect:Afficher uniquement les non-redirections.",
+ "apihelp-query+fileusage-example-simple": "Obtenir une liste des pages utilisant [[:File:Example.jpg]]",
+ "apihelp-query+fileusage-example-generator": "Obtenir l’information sur les pages utilisant [[:File:Example.jpg]]",
+ "apihelp-query+imageinfo-description": "Renvoyer l’information de fichier et l’historique de téléchargement.",
+ "apihelp-query+imageinfo-param-prop": "Quelles informations obtenir du fichier :\n;timestamp:Ajoute l’horodatage de la version téléchargée.\n;user:Ajoute l’utilisateur qui a téléchargé chaque version du fichier.\n;userid:Ajoute l’ID de l’utilisateur qui a téléchargé chaque version du fichier.\n;comment:Commentaire sur la version.\n;parsedcomment:Analyser le commentaire sur cette version.\n;canonicaltitle:Ajoute le titre canonique du fichier.\n;url:Fournit l’URL du fichier et la page de description.\n;size:Ajoute la taille du fichier en octets et la hauteur, la largeur et le nombre de pages (si applicable).\n;dimensions:Alias pour la taille.\n;sha1:Ajoute le hachage SHA-1 pour le fichier.\n;mime:Ajoute le type MIME du fichier.\n;thumbmime:Ajoute le type MIME de la vignette de l’image (nécessite l’URL et le paramètre $1urlwidth).\n;mediatype:Ajoute le type de média du fichier.\n;metadata:Liste les métadonnées Exif de la version du fichier.\n;commonmetadata:Liste les métadonnées génériques du format du fichier pour la version du fichier.\n;extmetadata:Liste les métadonnées mises en forme combinées depuis différentes sources. Les résultats sont au format HTML.\n;archivename:Ajoute le nom de fichier de la version d’archive pour les versions autres que la dernière.\n;bitdepth:Ajoute la profondeur de bit de la version.\n;uploadwarning:Utilisé par la page Special:Upload pour obtenir de l’information sur un fichier existant. Non prévu pour être utilisé en dehors du cœur de MédiaWiki.",
+ "apihelp-query+imageinfo-paramvalue-prop-timestamp": "Ajoute l’horodatage à la version téléchargée.",
+ "apihelp-query+imageinfo-paramvalue-prop-user": "Ajoute l’utilisateur qui a téléchargé chaque version du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-userid": "Ajouter l’ID de l’utilisateur qui a téléchargé chaque version du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-comment": "Commentaire sur la version.",
+ "apihelp-query+imageinfo-paramvalue-prop-parsedcomment": "Analyser le commentaire de la version.",
+ "apihelp-query+imageinfo-paramvalue-prop-canonicaltitle": "Ajoute le titre canonique du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-url": "Fournit l’URL du fichier et de la page de description.",
+ "apihelp-query+imageinfo-paramvalue-prop-size": "Ajoute la taille du fichier en octets et sa hauteur, largeur et compteur de page (le cas échéant).",
+ "apihelp-query+imageinfo-paramvalue-prop-dimensions": "Alias pour la taille.",
+ "apihelp-query+imageinfo-paramvalue-prop-sha1": "Ajoute le hachage SH1-1 du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-mime": "Ajoute le type MIME du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-thumbmime": "Ajoute le type MIME de la vignette de l’image (nécessite l’URL et le paramètre $1urlwidth).",
+ "apihelp-query+imageinfo-paramvalue-prop-mediatype": "Ajoute le type de média du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-metadata": "Liste les métadonnées Exif de la version du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-commonmetadata": "Liste les métadonnées génériques du format du fichier pour la version du fichier.",
+ "apihelp-query+imageinfo-paramvalue-prop-extmetadata": "Liste les métadonnées mises en forme combinées depuis diverses sources. Les résultats sont au format HTML.",
+ "apihelp-query+imageinfo-paramvalue-prop-archivename": "Ajoute le nom de fichier de la version d’archive pour les versions autres que la dernière.",
+ "apihelp-query+imageinfo-paramvalue-prop-bitdepth": "Ajoute la profondeur de bits de la version.",
+ "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "Utilisé par la page Special:Upload pour obtenir de l’information sur un fichier existant. Non prévu pour être utilisé en dehors du cœur de MédiaWiki.",
+ "apihelp-query+imageinfo-param-limit": "Combien de révision de fichier renvoyer par fichier.",
+ "apihelp-query+imageinfo-param-start": "Horodatage auquel démarrer la liste.",
+ "apihelp-query+imageinfo-param-end": "Horodatage auquel arrêter la liste.",
+ "apihelp-query+imageinfo-param-urlwidth": "Si $2prop=url est défini, une URL vers une image à l’échelle de cette largeur sera renvoyée.\nPour des raisons de performance si cette option est utilisée, pas plus de $1 images mises à l’échelle seront renvoyées.",
+ "apihelp-query+imageinfo-param-urlheight": "Similaire à $1urlwidth.",
+ "apihelp-query+imageinfo-param-metadataversion": "Version de métadonnées à utiliser. Si <kbd>latest</kbd> est spécifié, utiliser la dernière version. Par défaut à <kbd>1</kbd> pour la compatibilité ascendante.",
+ "apihelp-query+imageinfo-param-extmetadatalanguage": "Quelle langue pour analyser extmetadata. Cela affecte à la fois quelle traduction analyser, s’il y en a plusieurs, et comment les choses comme les nombres et d’autres valeurs sont mises en forme.",
+ "apihelp-query+imageinfo-param-extmetadatamultilang": "Si des traductions pour la propriété extmetadata sont disponibles, les analyser toutes.",
+ "apihelp-query+imageinfo-param-extmetadatafilter": "Si spécifié et non vide, seules ces clés seront renvoyées pour $1prop=extmetadata.",
+ "apihelp-query+imageinfo-param-urlparam": "Une chaîne de paramètre spécifique à l’analyseur. Par exemple, les PDFs peuvent utiliser <kbd>page15-100px</kbd>. <var>$1urlwidth</var> doit être utilisé et être cohérent avec <var>$1urlparam</var>.",
+ "apihelp-query+imageinfo-param-localonly": "Rechercher les fichiers uniquement dans le référentiel local.",
+ "apihelp-query+imageinfo-example-simple": "Analyser les informations sur la version actuelle de [[:File:Albert Einstein Head.jpg]]",
+ "apihelp-query+imageinfo-example-dated": "Analyser les informations sur les versions de [[:File:Test.jpg]] depuis 2008",
+ "apihelp-query+images-description": "Renvoie tous les fichiers contenus dans les pages fournies.",
+ "apihelp-query+images-param-limit": "Combien de fichiers renvoyer.",
+ "apihelp-query+images-param-images": "Lister uniquement ces fichiers. Utile pour vérifier si une page donnée contient un fichier donné.",
+ "apihelp-query+images-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+images-example-simple": "Obtenir une liste des fichiers utilisés dans [[Main Page]]",
+ "apihelp-query+images-example-generator": "Obtenir des informations sur tous les fichiers utilisés dans [[Main Page]]",
+ "apihelp-query+imageusage-description": "Trouver toutes les pages qui utilisent le titre de l’image donné.",
+ "apihelp-query+imageusage-param-title": "Titre à rechercher. Impossible à utiliser avec $1pageid.",
+ "apihelp-query+imageusage-param-pageid": "ID de la page à rechercher. Impossible à utiliser avec $1title.",
+ "apihelp-query+imageusage-param-namespace": "L’espace de noms à énumérer.",
+ "apihelp-query+imageusage-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+imageusage-param-filterredir": "Comment filtrer les redirections. Si mis à nonredirects quand $1redirect est activé, cela ne s’appliquera qu’au second niveau.",
+ "apihelp-query+imageusage-param-limit": "Combien de pages renvoyer au total. Si <var>$1redirect</var> est activé, la limite s’applique à chaque niveau séparément (ce qui veut dire que jusqu’à 2 * <var>$1limit</var> résultats peuvent être renvoyés).",
+ "apihelp-query+imageusage-param-redirect": "Si le lien vers une page est une redirection, trouver toutes les pages qui ont aussi un lien vers cette redirection. La limite maximale est divisée par deux.",
+ "apihelp-query+imageusage-example-simple": "Afficher les pages utilisant [[:File:Albert Einstein Head.jpg]]",
+ "apihelp-query+imageusage-example-generator": "Obtenir des informations sur les pages utilisant [[:File:Albert Einstein Head.jpg]]",
+ "apihelp-query+info-description": "Obtenir les informations de base sur la page.",
+ "apihelp-query+info-param-prop": "Quelles propriétés supplémentaires récupérer :\n;protection:Liste de niveau de protection de chaque page.\n;talkid:L’ID de la page de discussion pour chaque page qui n’est pas une page de discussion.\n;watched:Liste de l’état de suivi de chaque page.\n;watchers:Le nombre d’observateurs, si c&est autorisé.\n;notificationtimestamp:L’horodatage de notification de la liste de suivi de chaque page.\n;subjectid:L’ID de la page parente de chaque page de discussion.\n;url:Fournit une URL complète, une URL de modification, et l’URL canonique pour chaque page.\n;readable:Si l’utilisateur peut lire cette page.\n;preload:Fournit le texte renvoyé par EditFormPreloadText.\n;displaytitle:Fournit la manière dont le titre de la page est vraiment affiché.",
+ "apihelp-query+info-paramvalue-prop-protection": "Lister le niveau de protection de chaque page.",
+ "apihelp-query+info-paramvalue-prop-talkid": "L’ID de la page de discussion de chaque page qui n’est pas de discussion.",
+ "apihelp-query+info-paramvalue-prop-watched": "Lister l’état de suivi de chaque page.",
+ "apihelp-query+info-paramvalue-prop-watchers": "Le nombre d’observateurs, si c’est autorisé.",
+ "apihelp-query+info-paramvalue-prop-notificationtimestamp": "L’horodatage de notification de la liste de suivi de chaque page.",
+ "apihelp-query+info-paramvalue-prop-subjectid": "L’ID de page de la page parent de chaque page de discussion.",
+ "apihelp-query+info-paramvalue-prop-url": "Fournit une URL complète, une URL de modification, et l’URL canonique de chaque page.",
+ "apihelp-query+info-paramvalue-prop-readable": "Si l’utilisateur peut lire cette page.",
+ "apihelp-query+info-paramvalue-prop-preload": "Fournit le texte renvoyé par EditFormPreloadText.",
+ "apihelp-query+info-paramvalue-prop-displaytitle": "Fournit la manière dont le titre de la page est réellement affiché.",
+ "apihelp-query+info-param-testactions": "Tester si l’utilisateur actuel peut effectuer certaines actions sur la page.",
+ "apihelp-query+info-param-token": "Utiliser plutôt [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].",
+ "apihelp-query+info-example-simple": "Obtenir des informations sur la page <kbd>Main Page</kbd>.",
+ "apihelp-query+info-example-protection": "Obtenir des informations générale et de protection sur la page <kbd>Main Page</kbd>.",
+ "apihelp-query+iwbacklinks-description": "Trouver toutes les pages qui ont un lien vers le lien interwiki indiqué.\n\nPeut être utilisé pour trouver tous les liens avec un préfixe, ou tous les liens vers un titre (avec un préfixe donné). N’utiliser aucun paramètre revient en pratique à « tous les liens interwiki ».",
+ "apihelp-query+iwbacklinks-param-prefix": "Préfixe pour l’interwiki.",
+ "apihelp-query+iwbacklinks-param-title": "Lien interwiki à rechercher. Doit être utilisé avec <var>$1blprefix</var>.",
+ "apihelp-query+iwbacklinks-param-limit": "Combien de pages renvoyer.",
+ "apihelp-query+iwbacklinks-param-prop": "Quelles propriétés obtenir :\n;iwprefix:Ajoute le préfixe de l’interwiki.\n;iwtitle:Ajoute le titre de l’interwiki.",
+ "apihelp-query+iwbacklinks-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+iwbacklinks-example-simple": "Obtenir les pages ayant un lien vers [[wikibooks:Test]]",
+ "apihelp-query+iwbacklinks-example-generator": "Obtenir des informations sur les pages ayant un lien vers [[wikibooks:Test]]",
+ "apihelp-query+iwlinks-description": "Renvoie tous les liens interwiki des pages indiquées.",
+ "apihelp-query+iwlinks-param-url": "S&il faut obtenir l’URL complète (impossible à utiliser avec $1prop).",
+ "apihelp-query+iwlinks-param-prop": "Quelles propriétés supplémentaires obtenir pour chaque lien interlangue :\n;url:Ajoute l’URL complète.",
+ "apihelp-query+iwlinks-param-limit": "Combien de liens interwiki renvoyer.",
+ "apihelp-query+iwlinks-param-prefix": "Renvoyer uniquement les liens interwiki avec ce préfixe.",
+ "apihelp-query+iwlinks-param-title": "Lien interwiki à rechercher. Doit être utilisé avec <var>$1prefix</var>.",
+ "apihelp-query+iwlinks-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+iwlinks-example-simple": "Obtenir les liens interwiki de la page <kbd>Main Page</kbd>.",
+ "apihelp-query+langbacklinks-description": "Trouver toutes les pages qui ont un lien vers le lien de langue indiqué.\n\nPeut être utilisé pour trouver tous les liens avec un code de langue, ou tous les liens vers un titre (avec une langue donnée). N’utiliser aucun paramètre revient à « tous les liens de langue ».\n\nNotez que cela peut ne pas prendre en compte les liens de langue ajoutés par les extensions.",
+ "apihelp-query+langbacklinks-param-lang": "Langue pour le lien de langue.",
+ "apihelp-query+langbacklinks-param-title": "Lien interlangue à rechercher. Doit être utilisé avec $1lang.",
+ "apihelp-query+langbacklinks-param-limit": "Combien de pages renvoyer au total.",
+ "apihelp-query+langbacklinks-param-prop": "Quelles propriétés obtenir :\n;lllang:Ajoute le code de langue du lien de langue.\n;lltitle:Ajoute le titre du lien de langue.",
+ "apihelp-query+langbacklinks-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+langbacklinks-example-simple": "Obtenir les pages avec un lien avec [[:fr:Test]]",
+ "apihelp-query+langbacklinks-example-generator": "Obtenir des informations sur les pages ayant un lien vers [[:fr:Test]]",
+ "apihelp-query+langlinks-description": "Renvoie tous les liens interlangue des pages fournies.",
+ "apihelp-query+langlinks-param-limit": "Combien de liens interlangue renvoyer.",
+ "apihelp-query+langlinks-param-url": "S’il faut récupérer l’URL complète (impossible à utiliser avec <var>$1prop</var>).",
+ "apihelp-query+langlinks-param-prop": "Quelles propriétés supplémentaires obtenir pour chaque lien interlangue :\n;url:Ajoute l’URL complète.\n;langname:Ajoute le nom localisé de la langue (au mieux). Utiliser <var>$1inlanguagecode</var> pour contrôler la langue.\n;autonym:Ajoute le nom natif de la langue.",
+ "apihelp-query+langlinks-param-lang": "Renvoyer uniquement les liens interlangue avec ce code de langue.",
+ "apihelp-query+langlinks-param-title": "Lien à rechercher. Doit être utilisé avec <var>$1lang</var>.",
+ "apihelp-query+langlinks-param-dir": "La direction dans laquelle énumérer.",
+ "apihelp-query+langlinks-param-inlanguagecode": "Code de langue pour les noms de langue localisés.",
+ "apihelp-query+langlinks-example-simple": "Obtenir les liens interlangue de la page <kbd>Main Page</kbd>.",
+ "apihelp-query+links-description": "Renvoie tous les liens des pages fournies.",
+ "apihelp-query+links-param-namespace": "Afficher les liens uniquement dans ces espaces de nom.",
+ "apihelp-query+links-param-limit": "Combien de liens renvoyer.",
+ "apihelp-query+links-param-titles": "Lister uniquement les liens vers ces titres. Utile pour vérifier si une certaine page a un lien vers un titre donné.",
+ "apihelp-query+links-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+links-example-simple": "Obtenir les liens de la page <kbd>Main Page</kbd>",
+ "apihelp-query+links-example-generator": "Obtenir des informations sur tous les liens de page dans <kbd>Main Page</kbd>.",
+ "apihelp-query+links-example-namespaces": "Obtenir les liens de la page <kbd>Accueil</kbd> dans les espaces de nom {{ns:user}} et {{ns:template}}.",
+ "apihelp-query+linkshere-description": "Trouver toutes les pages ayant un lien vers les pages données.",
+ "apihelp-query+linkshere-param-prop": "Quelles propriétés obtenir :\n;pageid:ID de chaque page.\n;title:Titre de chaque page.\n;redirect:Indique si la page est une redirection.",
+ "apihelp-query+linkshere-param-namespace": "Inclure uniquement les pages dans ces espaces de nom.",
+ "apihelp-query+linkshere-param-limit": "Combien de résultats renvoyer.",
+ "apihelp-query+linkshere-param-show": "Afficher uniquement les éléments qui correspondent à ces critères :\n;redirect:Afficher uniquement les redirections.\n;!redirect:Afficher uniquement les non-redirections.",
+ "apihelp-query+linkshere-example-simple": "Obtenir une liste des pages liées à [[Main Page]]",
+ "apihelp-query+linkshere-example-generator": "Obtenir des informations sur les pages liées à [[Main Page]]",
+ "apihelp-query+logevents-description": "Obtenir des événements des journaux.",
+ "apihelp-query+logevents-param-prop": "Quelles propriétés obtenir :\n;ids:Ajoute l’ID de l’événement.\n;title:Ajoute le titre de la page pour l’événement.\n;type:Ajoute le type de l’événement.\n;user:Ajoute l’utilisateur responsable de l’événement.\n;userid:Ajoute l’ID de l’utilisateur responsable de l’événement.\n;timestamp:Ajoute l’horodatage de l’événement.\n;comment:Ajoute le commentaire de l’événement.\n;parsedcomment:Ajoute le commentaire analysé de l’événement.\n;details:Liste les détails supplémentaires sur l’événement.\n;tags:Liste les balises de l’événement.",
+ "apihelp-query+logevents-param-type": "Filtrer les entrées du journal à ce seul type.",
+ "apihelp-query+logevents-param-action": "Filtrer les actions du journal à cette seule action. Écrase <var>$1type</var>. Des actions avec une astérisque de la forme <var>$1type</var> sont autorisées pour spécifier n’importe quelle chaîne à la place de l’astérisque.",
+ "apihelp-query+logevents-param-start": "L’horodatage auquel démarrer l’énumération.",
+ "apihelp-query+logevents-param-end": "L’horodatage auquel arrêter l’énumération.",
+ "apihelp-query+logevents-param-user": "Restreindre aux entrées générées par l’utilisateur spécifié.",
+ "apihelp-query+logevents-param-title": "Restreindre aux entrées associées à une page donnée.",
+ "apihelp-query+logevents-param-namespace": "Restreindre aux entrées dans l’espace de nom spécifié.",
+ "apihelp-query+logevents-param-prefix": "Restreindre aux entrées commençant par ce préfixe.",
+ "apihelp-query+logevents-param-tag": "Lister seulement les entrées ayant cette balise.",
+ "apihelp-query+logevents-param-limit": "Combien d'entrées renvoyer au total.",
+ "apihelp-query+logevents-example-simple": "Liste les entrées de journal récentes.",
+ "apihelp-query+pagepropnames-description": "Lister les noms de toutes les propriétés de page utilisées sur le wiki.",
+ "apihelp-query+pagepropnames-param-limit": "Le nombre maximal de noms à renvoyer.",
+ "apihelp-query+pagepropnames-example-simple": "Obtenir les 10 premiers noms de propriété.",
+ "apihelp-query+pageprops-description": "Obtenir diverses propriétés définies dans le contenu de la page.",
+ "apihelp-query+pageprops-param-prop": "Lister uniquement ces propriétés. Utile pour vérifier si une certaine page utilise une certaine propriété de page.",
+ "apihelp-query+pageprops-example-simple": "Obtenir les propriétés de <kbd>Category:Foo</kbd>.",
+ "apihelp-query+pageswithprop-description": "Lister toutes les pages utilisant une propriété de page donnée.",
+ "apihelp-query+pageswithprop-param-propname": "Propriété de page pour laquelle énumérer les pages.",
+ "apihelp-query+pageswithprop-param-prop": "Quelles informations inclure :\n;ids:Ajoute l’ID de la page.\n;title:Ajoute le titre et l’ID de l’espace de noms de la page.\n;value:Ajoute la valeur de la propriété de page.",
+ "apihelp-query+pageswithprop-param-limit": "Le nombre maximal de pages à renvoyer.",
+ "apihelp-query+pageswithprop-param-dir": "Dans quelle direction trier.",
+ "apihelp-query+pageswithprop-example-simple": "Lister les 10 premières pages en utilisant <code>&#123;&#123;DISPLAYTITLE:&#125;&#125;</code>.",
+ "apihelp-query+pageswithprop-example-generator": "Obtenir des informations sur les 10 premières pages utilisant <code>_&#95;NOTOC_&#95;</code>.",
+ "apihelp-query+prefixsearch-description": "Effectuer une recherche de préfixe sur les titres de page.",
+ "apihelp-query+prefixsearch-param-search": "Chaîne de recherche.",
+ "apihelp-query+prefixsearch-param-namespace": "Espaces de nom à rechercher.",
+ "apihelp-query+prefixsearch-param-limit": "Nombre maximal de résultats à renvoyer.",
+ "apihelp-query+prefixsearch-param-offset": "Nombre de résultats à sauter.",
+ "apihelp-query+prefixsearch-example-simple": "Rechercher les titres de page commençant par <kbd>meaning</kbd>.",
+ "apihelp-query+protectedtitles-description": "Lister tous les titres protégés en création.",
+ "apihelp-query+protectedtitles-param-namespace": "Lister uniquement les titres dans ces espaces de nom.",
+ "apihelp-query+protectedtitles-param-level": "Lister uniquement les titres avec ces niveaux de protection.",
+ "apihelp-query+protectedtitles-param-limit": "Combien de pages renvoyer au total.",
+ "apihelp-query+protectedtitles-param-start": "Démarrer la liste à cet horodatage de protection.",
+ "apihelp-query+protectedtitles-param-end": "Arrêter la liste à cet horodatage de protection.",
+ "apihelp-query+protectedtitles-param-prop": "Quelles propriétés obtenir :\n;timestamp:Ajoute l’horodatage de l’ajout de la protection.\n;user:Ajoute l’utilisateur ayant ajouté la protection.\n;userid:Ajoute l’ID de l’utilisateur ayant ajouté la protection.\n;comment:Ajoute le commentaire de la protection.\n;parsedcomment:Ajoute le commentaire analysé de la protection.\n;expiry:Ajoute l’horodatage de levée de la protection.\n;level:Ajoute le niveau de protection.",
+ "apihelp-query+protectedtitles-example-simple": "Lister les titres protégés",
+ "apihelp-query+protectedtitles-example-generator": "Trouver les liens vers les titres protégés dans l’espace de noms principal",
+ "apihelp-query+querypage-description": "Obtenir une liste fournie par une page spéciale basée sur QueryPage",
+ "apihelp-query+querypage-param-page": "Le nom de la page spéciale. Remarque, ce nom est sensible à la casse.",
+ "apihelp-query+querypage-param-limit": "Nombre de résultats à renvoyer.",
+ "apihelp-query+querypage-example-ancientpages": "Renvoyer les résultats de [[Special:Ancientpages]].",
+ "apihelp-query+random-description": "Obtenir un ensemble de pages au hasard.\n\nLes pages sont listées dans un ordre prédéterminé, seul le point de départ est aléatoire. Par exemple, cela signifie que si la première page dans la liste est <samp>Accueil</samp>, la seconde sera <em>toujours</em> <samp>Liste des singes de fiction</samp>, la troisième <samp>Liste de personnes figurant sur les timbres de Vanuatu</samp>, etc.\n\nSi le nombre de page dans l’espace de nom est inférieur à <var>$1limit</var>, moins de pages seront renvoyées. La même page ne sera jamais renvoyée deux fois.",
+ "apihelp-query+random-param-namespace": "Renvoyer seulement des pages de ces espaces de noms.",
+ "apihelp-query+random-param-limit": "Limite sur le nombre de pages aléatoires renvoyées.",
+ "apihelp-query+random-param-redirect": "Charger une redirection aléatoire plutôt qu’une page aléatoire.",
+ "apihelp-query+random-example-simple": "Obtenir deux pages aléatoires de l’espace principal",
+ "apihelp-query+random-example-generator": "Renvoyer les informations de la page sur deux pages au hasard de l’espace de noms principal",
+ "apihelp-query+recentchanges-description": "Énumérer les modifications récentes.",
+ "apihelp-query+recentchanges-param-start": "L’horodatage auquel démarrer l’énumération.",
+ "apihelp-query+recentchanges-param-end": "L’horodatage auquel arrêter l’énumération.",
+ "apihelp-query+recentchanges-param-namespace": "Filtrer les modifications uniquement sur ces espaces de nom.",
+ "apihelp-query+recentchanges-param-user": "Lister uniquement les modifications par cet utilisateur.",
+ "apihelp-query+recentchanges-param-excludeuser": "Ne pas lister les modifications par cet utilisateur.",
+ "apihelp-query+recentchanges-param-tag": "Lister uniquement les modifications marquées avec cette balise.",
+ "apihelp-query+recentchanges-param-prop": "Inclure des informations supplémentaires :\n;user:Ajoute l’utilisateur responsable de la modification et marque si c’est une adresse IP.\n;userid:Ajoute l’ID de l’utilisateur responsable de la modification.\n;comment:Ajoute le commentaire de la modification.\n;parsedcomment:Ajoute le commentaire analysé pour la modification.\n;flags:Ajoute les balises de la modification.\n;timestamp:Ajoute l’horodatage de la modification.\n;title:Ajoute le titre de la page modifiée.\n;ids:Ajoute l’ID de la page, l’ID des modifications récentes et l’ID de l’ancienne et la nouvelle révisions.\n;sizes:Ajoute l’ancienne et la nouvelle tailles de la page en octets.\n;redirect:Marque la modification si la page est une redirection.\n;patrolled:Marque les modifications patrouillables comme patrouillées ou non.\n;loginfo:Ajoute les informations du journal (Id du journal, type de trace, etc.) aux entrées du journal.\n;tags:Liste les balises de l’entrée.\n;sha1:Ajoute la somme de contrôle du contenu pour les entrées associées à une révision.",
+ "apihelp-query+recentchanges-param-token": "Utiliser plutôt <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
+ "apihelp-query+recentchanges-param-show": "Afficher uniquement les éléments correspondant à ces critères. Par exemple, pour voir uniquement les modifications mineures par des utilisateurs connectés, mettre $1show=minor|!anon.",
+ "apihelp-query+recentchanges-param-limit": "Combien de modifications renvoyer au total.",
+ "apihelp-query+recentchanges-param-type": "Quels types de modification afficher.",
+ "apihelp-query+recentchanges-param-toponly": "Lister uniquement les modifications qui sont de la dernière révision.",
+ "apihelp-query+recentchanges-example-simple": "Lister les modifications récentes",
+ "apihelp-query+recentchanges-example-generator": "Obtenir l’information de page sur les modifications récentes non patrouillées",
+ "apihelp-query+redirects-description": "Renvoie toutes les redirections vers les pages données.",
+ "apihelp-query+redirects-param-prop": "Quelles propriétés récupérer :\n;pageid:ID de page de chaque redirection.\n;title:Titre de chaque redirection.\n;fragment:Fragment de chaque redirection, s’il y en a un.",
+ "apihelp-query+redirects-param-namespace": "Inclure uniquement les pages dans ces espaces de nom.",
+ "apihelp-query+redirects-param-limit": "Combien de redirections renvoyer.",
+ "apihelp-query+redirects-param-show": "Afficher uniquement les éléments correspondant à ces critères :\n;fragment:Afficher uniquement les redirections avec un fragment.\n;!fragment:Afficher uniquement les redirections sans fragment.",
+ "apihelp-query+redirects-example-simple": "Obtenir une liste des redirections vers [[Main Page]]",
+ "apihelp-query+redirects-example-generator": "Obtenir des informations sur toutes les redirections vers [[Main Page]]",
+ "apihelp-query+revisions-description": "Obtenir des informations sur la révision.\n\nPeut être utilisé de différentes manières :\n# Obtenir des données sur un ensemble de pages (dernière révision), en mettant les titres ou les ids de page.\n# Obtenir les révisions d’une page donnée, en utilisant les titres ou les ids de page avec début, fin ou limite.\n# Obtenir des données sur un ensemble de révisions en donnant leurs IDs et leurs ids de révision.",
+ "apihelp-query+revisions-paraminfo-singlepageonly": "Utilisable uniquement avec une seule page (mode #2).",
+ "apihelp-query+revisions-param-startid": "À quel ID de révision démarrer l’énumération.",
+ "apihelp-query+revisions-param-endid": "Arrêter l’énumération des révisions à cet ID.",
+ "apihelp-query+revisions-param-start": "À quel horodatage de révision démarrer l’énumération.",
+ "apihelp-query+revisions-param-end": "Énumérer jusqu’à cet horodatage.",
+ "apihelp-query+revisions-param-user": "Inclure uniquement les révisions faites par l’utilisateur.",
+ "apihelp-query+revisions-param-excludeuser": "Exclure les révisions faites par l’utilisateur.",
+ "apihelp-query+revisions-param-tag": "Lister uniquement les révisions marquées avec cette balise.",
+ "apihelp-query+revisions-param-token": "Quels jetons obtenir pour chaque révision.",
+ "apihelp-query+revisions-example-content": "Obtenir des données avec le contenu pour la dernière révision des titres <kbd>API</kbd> et <kbd>Page principale</kbd>.",
+ "apihelp-query+revisions-example-last5": "Obtenir les 5 dernières révisions de la <kbd>Main Page</kbd>.",
+ "apihelp-query+revisions-example-first5": "Obtenir les 5 premières révisions de la <kbd>Page principale</kbd>.",
+ "apihelp-query+revisions-example-first5-after": "Obtenir les 5 premières révisions de la <kbd>Page principale</kbd> faites après le 01/05/2006.",
+ "apihelp-query+revisions-example-first5-not-localhost": "Obtenir les 5 premières révisions de la <kbd>Page principale</kbd> qui n’ont pas été faites par l’utilisateur anonyme <kbd>127.0.0.1</kbd>.",
+ "apihelp-query+revisions-example-first5-user": "Obtenir les 5 premières révisions de la <kbd>Page principale</kbd> qui ont été faites par l’utilisateur <kbd>MédiaWiki par défaut</kbd>.",
+ "apihelp-query+revisions+base-param-prop": "Quelles propriétés obtenir pour chaque révision :\n;ids:L’ID de la révision.\n;flags:Marques de la révision (mineure).\n;timestamp:L’horodatage de la révision.\n;user:Utilisateur ayant fait la révision.\n;userid:ID de l’utilisateur ayant créé la révision.\n;size:Taille (en octets) de la révision.\n;sha1:SHA-1 (base 16) de la révision.\n;contentmodel:ID du modèle de contenu de la révision.\n;comment:Commentaire par l’utilisateur de la révision.\n;parsedcomment:Commentaire analysé par l’utilisateur de la révision.\n;content:Texte de la révision.\n;tags:Balises de la révision.",
+ "apihelp-query+revisions+base-param-limit": "Limiter le nombre de révisions retournées.",
+ "apihelp-query+revisions+base-param-expandtemplates": "Développer les modèles dans le contenu de la révision (nécessite $1prop=content).",
+ "apihelp-query+revisions+base-param-generatexml": "Générer l’arbre d’analyse XML pour le contenu de la révision (nécessite $1prop=content).",
+ "apihelp-query+revisions+base-param-parse": "Analyser le contenu de la révision (nécessite $1prop=content). Pour des raisons de performance, si cette option est utilisée, $1limit est forcé à 1.",
+ "apihelp-query+revisions+base-param-section": "Récupérer uniquement le contenu de ce numéro de section.",
+ "apihelp-query+revisions+base-param-diffto": "ID de révision à comparer à chaque révision. Utiliser <kbd>prev</kbd>, <kbd>next</kbd> et <kbd>cur</kbd> pour la version précédente, suivante et actuelle respectivement.",
+ "apihelp-query+revisions+base-param-difftotext": "Texte auquel comparer chaque révision. Compare uniquement un nombre limité de révisions. Écrase <var>$1diffto</var>. Si <var>$1section</var> est positionné, seule cette section sera comparée avec ce texte",
+ "apihelp-query+revisions+base-param-contentformat": "Format de sérialisation utilisé pour <var>$1difftotext</var> et attendu pour la sortie du contenu.",
+ "apihelp-query+search-description": "Effectuer une recherche en texte intégral.",
+ "apihelp-query+search-param-search": "Rechercher les titres (ou le contenu) de toutes les pages ayant cette valeur.",
+ "apihelp-query+search-param-namespace": "Rechercher uniquement dans ces espaces de nom.",
+ "apihelp-query+search-param-what": "Quel type de recherche effectuer.",
+ "apihelp-query+search-param-info": "Quelles métadonnées renvoyer.",
+ "apihelp-query+search-param-prop": "Quelles propriétés renvoyer :\n;size:Ajoute la taille de la page en octets.\n;wordcount:Ajoute le nombre de mots de la page.\n;timestamp:Ajoute l’horodatage de la dernière modification de la page.\n;snippet:Ajoute un extrait analysé de la page.\n;titlesnippet:Ajoute un extrait analysé du titre de la page.\n;redirectsnippet:Ajoute un extrait analysé du titre de la redirection.\n;redirecttitle:Ajoute le titre de la redirection correspondante.\n;sectionsnippet:Ajoute un extrait analysé du titre de la section correspondante.\n;sectiontitle:Ajoute le titre de la section correspondante.\n;score:<span class=\"apihelp-deprecated\">Obsolète et ignoré.</span>\n;hasrelated:<span class=\"apihelp-deprecated\">Obsolète et ignoré.</span>",
+ "apihelp-query+search-param-limit": "Combien de pages renvoyer au total.",
+ "apihelp-query+search-param-interwiki": "Inclure les résultats interwiki dans la recherche, s’ils sont disponibles.",
+ "apihelp-query+search-param-backend": "Quel serveur de recherche utiliser, si ce n’est pas celui par défaut.",
+ "apihelp-query+search-example-simple": "Rechercher <kbd>signification </kbd>.",
+ "apihelp-query+search-example-text": "Rechercher des textes pour <kbd>signification</kbd>.",
+ "apihelp-query+search-example-generator": "Obtenir les informations sur les pages renvoyées par une recherche de <kbd>signification</kbd>.",
+ "apihelp-query+siteinfo-description": "Renvoyer les informations générales sur le site.",
+ "apihelp-query+siteinfo-param-prop": "Quelles informations obtenir :\n;general:Information globale du système.\n;namespaces:Liste des espaces de nom déclarés et leur nom canonique.\n;namespacealiases:Liste des alias des espaces de nom déclarés.\n;specialpagealiases:Liste des alias des pages spéciales.\n;magicwords:Liste des mots magiques et leurs alias.\n;statistics:Renvoie les statistiques du site.\n;interwikimap:Renvoie la correspondance interwiki (éventuellement filtrée, éventuellement localisée en utilisant <var>$1inlanguagecode</var>).\n;dbrepllag:Renvoie le serveur de base de donnée avec la plus grande latence de réplication.\n;usergroups:Renvoie les groupes utilisateur et les droits associés.\n;libraries:Renvoie les bibliothèques installées sur le wiki.\n;extensions:Renvoie les extensions installées sur le wiki.\n;fileextensions:Renvoie la liste des extensions de fichier autorisées au téléchargement.\n;rightsinfo:Renvoie l’information sur les droits du wiki (sa licence), si elle est disponible.\n;restrictions:Renvoie l’information sur les types de restriction disponibles (protection).\n;languages:Renvoie une liste des langues que supporte MédiaWiki (éventuellement localisé en utilisant <var>$1inlanguagecode</var>).\n;skins:Renvoie une liste de tous les habillages activés (éventuellement localisé en utilisant <var>$1inlanguagecode</var>, sinon dans la langue du contenu).\n;extensiontags:Renvoie une liste des balises d’extension de l’analyseur.\n;functionhooks:Renvoie une liste des accroches de fonction de l’analyseur.\n;showhooks:Renvoie une liste de toutes les accroches souscrites (contenu de <var>[[mw:Manual:$wgHooks|$wgHooks]]</var>).\n;variables:Renvoie une liste des IDs de variable.\n;protocols:Renvoie une liste des protocoles qui sont autorisés dans les liens externes.\n;defaultoptions:Renvoie les valeurs par défaut pour les préférences utilisateur.",
+ "apihelp-query+siteinfo-param-filteriw": "Renvoyer uniquement les entrées locales ou uniquement les non locales de la correspondance interwiki.",
+ "apihelp-query+siteinfo-param-showalldb": "Lister tous les serveurs de base de données, pas seulement celui avec la plus grande latence.",
+ "apihelp-query+siteinfo-param-numberingroup": "Liste le nombre d’utilisateurs dans les groupes.",
+ "apihelp-query+siteinfo-param-inlanguagecode": "Code de langue pour les noms de langue localisés (du mieux possible) et les noms d’habillage.",
+ "apihelp-query+siteinfo-example-simple": "Extraire les informations du site",
+ "apihelp-query+siteinfo-example-interwiki": "Extraire une liste des préfixes interwiki locaux",
+ "apihelp-query+siteinfo-example-replag": "Vérifier la latence de réplication actuelle",
+ "apihelp-query+stashimageinfo-description": "Renvoie les informations de fichier des fichiers mis en réserve.",
+ "apihelp-query+stashimageinfo-param-filekey": "Clé qui identifie un téléchargement précédent qui a été temporairement mis en réserve.",
+ "apihelp-query+stashimageinfo-param-sessionkey": "Alias pour $1filekey, pour la compatibilité descendante.",
+ "apihelp-query+stashimageinfo-example-simple": "Renvoie les informations sur un fichier mis en réserve.",
+ "apihelp-query+stashimageinfo-example-params": "Renvoie les vignettes pour deux fichiers mis en réserve",
+ "apihelp-query+tags-description": "Lister les balises de modification.",
+ "apihelp-query+tags-param-limit": "Le nombre maximal de balises à lister.",
+ "apihelp-query+tags-param-prop": "Quelles propriétés récupérer :\n;name:Ajoute le nom de la balise.\n;displayname:Ajoute le message système pour la balise.\n;description:Ajoute la description de la balise.\n;hitcount:Ajoute le nombre de révisions et d’entrées du journal qui ont cette balise.\n;defined:Indique si la balise est définie.\n;source:Obtient les sources de la balise, ce qui comprend <samp>extension</samp> pour les balises définies par une extension et <samp>manual</samp> pour les balises pouvant être appliquées manuellement par les utilisateurs.\n;active:Si la balise est encore appliquée.",
+ "apihelp-query+tags-example-simple": "Lister les balises disponibles",
+ "apihelp-query+templates-description": "Renvoie toutes les pages incluses dans les pages fournies.",
+ "apihelp-query+templates-param-namespace": "Afficher les modèles uniquement dans ces espaces de nom.",
+ "apihelp-query+templates-param-limit": "Combien de modèles renvoyer.",
+ "apihelp-query+templates-param-templates": "Lister uniquement ces modèles. Utile pour vérifier si une certaine page utilise un modèle donné.",
+ "apihelp-query+templates-param-dir": "La direction dans laquelle lister.",
+ "apihelp-query+templates-example-simple": "Obtenir les modèles utilisés sur la page <kbd>Accueil</kbd>.",
+ "apihelp-query+templates-example-generator": "Obtenir des informations sur les pages modèle utilisé sur <kbd>Main Page</kbd>.",
+ "apihelp-query+templates-example-namespaces": "Obtenir les pages des espaces de nom {{ns:user}} et {{ns:template}} qui sont inclues dans la page <kdb>Main Page<kdb>.",
+ "apihelp-query+tokens-description": "Récupère les jetons pour les actions de modification de données.",
+ "apihelp-query+tokens-param-type": "Types de jeton à demander.",
+ "apihelp-query+tokens-example-simple": "Récupérer un jeton csrf (par défaut)",
+ "apihelp-query+tokens-example-types": "Récupérer un jeton de suivi et un de patrouille",
+ "apihelp-query+transcludedin-description": "Trouver toutes les pages qui incluent les pages données.",
+ "apihelp-query+transcludedin-param-prop": "Quelles propriétés obtenir :\n;pageid:ID de page de chaque page.\n;title:Titre de chaque page.\n;redirect:Marque si cette page est une redirection.",
+ "apihelp-query+transcludedin-param-namespace": "Inclure uniquement les pages dans ces espaces de nom.",
+ "apihelp-query+transcludedin-param-limit": "Combien en renvoyer.",
+ "apihelp-query+transcludedin-param-show": "Afficher uniquement les éléments qui correspondent à ces critères:\n;redirect:Afficher uniquement les redirections.\n;!redirect:Afficher uniquement les non-redirections.",
+ "apihelp-query+transcludedin-example-simple": "Obtenir une liste des pages incluant <kbd>Main Page</kbd>.",
+ "apihelp-query+transcludedin-example-generator": "Obtenir des informations sur les pages incluant <kbd>Main Page</kbd>.",
+ "apihelp-query+usercontribs-description": "Obtenir toutes les modifications par un utilisateur.",
+ "apihelp-query+usercontribs-param-limit": "Le nombre maximal de contributions à renvoyer.",
+ "apihelp-query+usercontribs-param-start": "L’horodatage auquel démarrer le retour.",
+ "apihelp-query+usercontribs-param-end": "L’horodatage auquel arrêter le retour.",
+ "apihelp-query+usercontribs-param-user": "Les utilisateurs pour lesquels récupérer les contributions.",
+ "apihelp-query+usercontribs-param-userprefix": "Récupérer les contributions pour tous les utilisateurs dont les noms commencent par cette valeur. Écrase $1user.",
+ "apihelp-query+usercontribs-param-namespace": "Lister uniquement les contributions dans ces espaces de nom.",
+ "apihelp-query+usercontribs-param-prop": "Inclure des informations supplémentaires:\n;ids:Ajoute l’ID de page et l’ID de révision.\n;title:Ajoute le titre et l’ID d’espace de noms de la page.\n;timestamp:Ajoute l’horodatage de la modification.\n;comment:Ajoute le commentaire de la modification.\n;parsedcomment:Ajoute le commentaire analysé de la modification.\n;size:Ajoute la nouvelle taille de la modification.\n;sizediff:Ajoute le delta de taille de la modification par rapport à son parent.\n;flags:Ajoute les marques de la modification.\n;patrolled:Marque les modifications patrouillées.\n;tags:Liste les balises de la modification.",
+ "apihelp-query+usercontribs-param-show": "Afficher uniquement les éléments correspondant à ces critères, par ex. les modifications non mineures uniquement : <kbd>$2show=!minor</kbd>.\n\nSi <kbd>$2show=patrolled</kbd> ou <kbd>$2show=!patrolled</kbd> est positionné, les révisions plus anciennes que <var>[[mw:Manual:$wgRCMaxAge|$wgRCMaxAge]]</var> ($1 {{PLURAL:$1|seconde|secondes}}) ne seront pas affichées.",
+ "apihelp-query+usercontribs-param-tag": "Lister uniquement les révisions marquées avec cette balise.",
+ "apihelp-query+usercontribs-param-toponly": "Lister uniquement les modifications qui sont la dernière révision.",
+ "apihelp-query+usercontribs-example-user": "Afficher les contributions de l'utilisateur <kbd>Exemple</kbd>.",
+ "apihelp-query+usercontribs-example-ipprefix": "Afficher les contributions de toutes les adresses IP avec le préfixe <kbd>192.0.2.</kbd>.",
+ "apihelp-query+userinfo-description": "Obtenir de l’information sur l’utilisateur courant.",
+ "apihelp-query+userinfo-param-prop": "Quelles informations inclure :\n;blockinfo:Marque si l’utilisateur actuel est bloqué, par qui, et pour quelle raison.\n;hasmsg:Ajoute une balise <samp>message</samp> si l’utilisateur actuel a des messages en cours.\n;groups:Liste tous les groupes auxquels appartient l’utilisateur actuel.\n;implicitgroups:Liste tous les groupes dont l’utilisateur actuel est automatiquement membre.\n;rights:Liste tous les droits qu’a l’utilisateur actuel.\n;changeablegroups:Liste les groupes pour lesquels l’utilisateur actuel peut ajouter ou supprimer.\n;options:Liste toutes les préférences qu’a défini l’utilisateur actuel.\n;preferencestoken:<span class=\"apihelp-deprecated\">Obsolete.</span> Obtient un jeton pour modifier les préférences de l’utilisateur actuel.\n;editcount:Ajoute le compteur de modifications de l’utilisateur actuel.\n;ratelimits:Liste toutes les limites de débit s’appliquant à l’utilisateur actuel.\n;realname:Ajoute le vrai nom de l’utilisateur actuel.\n;email:Ajoute l’adresse de courriel de l’utilisateur et sa date d’authentification.\n;acceptlang:Renvoie en écho l’entête <code>Accept-Language</code> envoyé par le client dans un format structuré.\n;registrationdate:Ajoute la date d’inscription de l’utilisateur.\n;unreadcount:Ajoute le compteur de pages non lues de la liste de suivi de l’utilisateur (au maximum $1 ; renvoie <samp>$2</samp> s’il y en a plus).",
+ "apihelp-query+userinfo-example-simple": "Obtenir de l’information sur l’utilisateur actuel",
+ "apihelp-query+userinfo-example-data": "Obtenir des informations supplémentaires sur l’utilisateur actuel",
+ "apihelp-query+users-description": "Obtenir des information sur une liste d’utilisateurs",
+ "apihelp-query+users-param-prop": "Quelles informations inclure :\n;blockinfo:Marque si l’utilisateur est bloqué, par qui, et pour quelle raison.\n;groups:Liste tous les groupes auquel appartient chaque utilisateur.\n;implicitgroups:Liste tous les groupes dont un utilisateur est automatiquement membre.\n;rights:Liste tous les droits qu’a un utilisateur.\n;editcount:Ajoute le compteur de modifications de l’utilisateur.\n;registration:Ajoute l’horodatage d’inscription de l’utilisateur.\n;emailable:Marque si l’utilisateur peut et veut recevoir des courriels via [[Special:Emailuser]].\n;gender:Marque le sexe de l’utilisateur. Renvoie « male », « female », ou « unknown ».",
+ "apihelp-query+users-param-users": "Une liste des utilisateurs sur lesquels obtenir de l’information.",
+ "apihelp-query+users-param-token": "Utiliser plutôt <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
+ "apihelp-query+users-example-simple": "Renvoyer des informations pour l'utilisateur <kbd>Exemple</kbd>.",
+ "apihelp-query+watchlist-description": "Obtenir les modifications récentes des pages dans la liste de suivi de l’utilisateur actuel.",
+ "apihelp-query+watchlist-param-allrev": "Inclure les multiples révisions de la même page dans l’intervalle de temps fourni.",
+ "apihelp-query+watchlist-param-start": "L’horodatage auquel démarrer l’énumération.",
+ "apihelp-query+watchlist-param-end": "L’horodatage auquel arrêter l’énumération.",
+ "apihelp-query+watchlist-param-namespace": "Filtrer les modifications aux seuls espaces de nom fournis.",
+ "apihelp-query+watchlist-param-user": "Lister uniquement les modifications par cet utilisateur.",
+ "apihelp-query+watchlist-param-excludeuser": "Ne pas lister les modifications faites par cet utilisateur.",
+ "apihelp-query+watchlist-param-limit": "Combien de résultats au total renvoyer par demande.",
+ "apihelp-query+watchlist-param-prop": "Quels éléments supplémentaires obtenir :\n;ids:Ajoute les IDs de révision et de page.\n;title:Ajoute le titre de la page.\n;flags:Ajoute les marques de la modification.\n;user:Ajoute l’utilisateur ayant fait la modification.\n;userid:Ajoute l’ID de l’utilisateur ayant fait la modification.\n;comment:Ajoute le commentaire de la modification.\n;parsedcomment:Ajoute le commentaire analysé de la modification.\n;timestamp:Ajoute l’horodatage de la modification.\n;patrol:Marque les modifications patrouillées.\n;sizes:Ajoute les ancienne et nouvelle tailles de la page.\n;notificationtimestamp:Ajoute l’horodatage de quand l’utilisateur a été notifié de la modification la dernière fois.\n;loginfo:Ajoute l’information du journal quand c’est approprié.",
+ "apihelp-query+watchlist-param-show": "Afficher uniquement les éléments qui correspondent à ces critères. Par exemple, pour voir uniquement les modifications mineures faites par des utilisateurs connectés, mettre $1show=minor|!anon.",
+ "apihelp-query+watchlist-param-type": "Quels types de modification afficher :\n;edit:Modifications de page normale.\n;external:Modifications externes.\n;new:Créations de page.\n;log:Entrées du journal.",
+ "apihelp-query+watchlist-param-owner": "Utilisé avec $1token pour accéder à la liste de suivi d’un autre utilisateur.",
+ "apihelp-query+watchlist-param-token": "Un jeton de sécurité (disponible dans les [[Special:Preferences#mw-prefsection-watchlist|préférences]] de l’utilsiateur) pour autoriser l’accès à la liste de suivi d&un autre utilisateur.",
+ "apihelp-query+watchlist-example-simple": "Lister la révision de tête des pages récemment modifiées dans la liste de suivi de l’utilisateur actuel",
+ "apihelp-query+watchlist-example-props": "Chercher des informations supplémentaires sur la révision de tête des pages récemment modifiées de la liste de suivi de l’utilisateur actuel",
+ "apihelp-query+watchlist-example-allrev": "Chercher les informations sur toutes les modifications récentes des pages de la liste de suivi de l’utilisateur actuel",
+ "apihelp-query+watchlist-example-generator": "Chercher l’information de la page sur les pages récemment modifiées de la liste de suivi de l’utilisateur actuel",
+ "apihelp-query+watchlist-example-generator-rev": "Chercher l’information de la révision pour les modifications récentes des pages de la liste de suivi de l’utilisateur actuel",
+ "apihelp-query+watchlist-example-wlowner": "Lister la révision de tête des pages récemment modifiées de la liste de suivi de l'utilisateur <kbd>Exemple</kbd>.",
+ "apihelp-query+watchlistraw-description": "Obtenir toutes les pages de la liste de suivi de l’utilisateur actuel.",
+ "apihelp-query+watchlistraw-param-namespace": "Lister uniquement les pages dans les espaces de nom fournis.",
+ "apihelp-query+watchlistraw-param-limit": "Combien de résultats renvoyer au total par requête.",
+ "apihelp-query+watchlistraw-param-prop": "Quelles propriétés supplémentaires obtenir :\n;changed:Ajoute l’horodatage de la dernière notification de l’utilisateur à propos de la modification.",
+ "apihelp-query+watchlistraw-param-show": "Lister uniquement les éléments correspondant à ces critères.",
+ "apihelp-query+watchlistraw-param-owner": "Utilisé avec $1token pour accéder à la liste de suivi d’un autre utilisateur.",
+ "apihelp-query+watchlistraw-param-token": "Un jeton de sécurité (disponible dans les [[Special:Preferences#mw-prefsection-watchlist|préférences]] de l’utilisateur) pour permettre l’accès à la liste de suivi d’un autre utilisateur.",
+ "apihelp-query+watchlistraw-example-simple": "Lister les pages dans la liste de suivi de l’utilisateur actuel",
+ "apihelp-query+watchlistraw-example-generator": "Chercher l’information sur les pages de la liste de suivi de l’utilisateur actuel",
+ "apihelp-revisiondelete-description": "Supprimer et annuler la suppression des révisions.",
+ "apihelp-revisiondelete-param-type": "Type de suppression de révision en cours de traitement.",
+ "apihelp-revisiondelete-param-target": "Titre de page pour la suppression de révision, s’il est nécessaire pour le type.",
+ "apihelp-revisiondelete-param-ids": "Identifiants pour les révisions à supprimer.",
+ "apihelp-revisiondelete-param-hide": "Quoi masquer pour chaque révision.",
+ "apihelp-revisiondelete-param-show": "Quoi démasquer pour chaque révision",
+ "apihelp-revisiondelete-param-suppress": "S’il faut supprimer les données aux administrateurs comme aux autres.",
+ "apihelp-revisiondelete-param-reason": "Motif de suppression ou d’annulation de suppression.",
+ "apihelp-revisiondelete-example-revision": "Masquer le contenu de la révision <kbd>12345</kbd> de la page <kbd>Main Page</kbd>",
+ "apihelp-revisiondelete-example-log": "Masquer toutes les données de l’entrée de journal <kbd>67890</kbd> avec le motif <kbd>Violation de Biographie de Personne Vivante</kbd>.",
+ "apihelp-rollback-description": "Annuler la dernière modification de la page.\n\nSi le dernier utilisateur à avoir modifié la page a fait plusieurs modifications sur une ligne, elles seront toutes annulées.",
+ "apihelp-rollback-param-title": "Titre de la page à restaurer. Impossible à utiliser avec <var>$1pageid</var>.",
+ "apihelp-rollback-param-pageid": "ID de la page à restaurer. Impossible à utiliser avec <var>$1title</var>.",
+ "apihelp-rollback-param-user": "Nom de l’utilisateur dont les modifications doivent être annulées.",
+ "apihelp-rollback-param-summary": "Personnaliser le résumé de la modification. S’il est vide, le résumé par défaut sera utilisé.",
+ "apihelp-rollback-param-markbot": "Marquer les modifications annulées et les modifications annulées comme robot.",
+ "apihelp-rollback-param-watchlist": "Ajouter ou supprimer la page de la liste de suivi de l’utilisateur actuel sans condition, utiliser les préférences ou ne pas modifier le suivi.",
+ "apihelp-rollback-example-simple": "Annuler les dernières modifications à [<kbd>Main Page</kbd> par l’utilisateur <kbd>Exemple</kbd>.",
+ "apihelp-rollback-example-summary": "Annuler les dernières modifications de la page <kbd>Main Page</kbd> par l’utilisateur à l’adresse IP <kbd>192.0.2.5</kbd> avec le résumé <kbd>Annulation de vandalisme<kbd>, et marquer ces modifications et l’annulation comme modifications de robots.",
+ "apihelp-rsd-description": "Exporter un schéma RSD (Découverte Très Simple).",
+ "apihelp-rsd-example-simple": "Exporter le schéma RSD",
+ "apihelp-setnotificationtimestamp-description": "Mettre à jour l’horodatage de notification pour les pages suivies.\n\nCela affecte la mise en évidence des pages modifiées dans la liste de suivi et l’historique, et l’envoi de courriel quand la préférence « M’envoyer un courriel quand une page de ma liste de suivi est modifiée » est activée.",
+ "apihelp-setnotificationtimestamp-param-entirewatchlist": "Travailler sur toutes les pages suivies.",
+ "apihelp-setnotificationtimestamp-param-timestamp": "Horodatage auquel dater la notification.",
+ "apihelp-setnotificationtimestamp-param-torevid": "Révision pour laquelle fixer l’horodatage de notification (une page uniquement).",
+ "apihelp-setnotificationtimestamp-param-newerthanrevid": "Révision pour fixer l’horodatage de notification plus récent (une page uniquement).",
+ "apihelp-setnotificationtimestamp-example-all": "Réinitialiser l’état de notification pour toute la liste de suivi",
+ "apihelp-setnotificationtimestamp-example-page": "Réinitialiser l’état de notification pour la <kbd>Page principale<kbd>.",
+ "apihelp-setnotificationtimestamp-example-pagetimestamp": "Fixer l’horodatage de notification pour <kbd>Page principale</kbd> afin que toutes les modifications depuis le 1 janvier 2012 soient non vues",
+ "apihelp-setnotificationtimestamp-example-allpages": "Réinitialiser l’état de notification sur les pages dans l’espace de noms <kbd>{{ns:user}}</kbd>.",
+ "apihelp-tokens-description": "Obtenir les jetons pour les actions modifiant les données.\n\nCe module est obsolète, remplacé par [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].",
+ "apihelp-tokens-param-type": "Types de jeton à demander.",
+ "apihelp-tokens-example-edit": "Récupérer un jeton de modification (par défaut).",
+ "apihelp-tokens-example-emailmove": "Récupérer un jeton de courriel et un jeton de déplacement.",
+ "apihelp-unblock-description": "Débloquer un utilisateur.",
+ "apihelp-unblock-param-id": "ID du blocage à lever (obtenu via <kbd>list=blocks</kbd>). Impossible à utiliser avec <var>$1user</var>.",
+ "apihelp-unblock-param-user": "Nom d’utilisateur, adresse IP ou plage d’adresse IP à débloquer. Impossible à utiliser avec <var>$1id</var>.",
+ "apihelp-unblock-param-reason": "Motif de déblocage.",
+ "apihelp-unblock-example-id": "Lever le blocage d’ID #<kbd>105</kbd>.",
+ "apihelp-unblock-example-user": "Débloquer l’utilisateur <kbd>Bob</kbd> avec le motif <kbd>Désolé Bob</kbd>.",
+ "apihelp-undelete-description": "Restaurer les révisions d’une page supprimée.\n\nUne liste des révisions supprimées (avec les horodatages) peut être récupérée via [[Special:ApiHelp/query+deletedrevs|list=deletedrevs]], et une liste d’IDs de fichier supprimé peut être récupérée via [[Special:ApiHelp/query+filearchive|list=filearchive]].",
+ "apihelp-undelete-param-title": "Titre de la page à restaurer.",
+ "apihelp-undelete-param-reason": "Motif de restauration.",
+ "apihelp-undelete-param-timestamps": "Horodatages des révisions à restaurer. Si <var>$1timestamps</var> et <var>$1fileids</var> sont vides, toutes seront restaurées.",
+ "apihelp-undelete-param-fileids": "IDs des révisions de fichier à restaurer. Si <var>$1timestamps</var> et <var>$1fileids</var> sont vides, toutes seront restaurées.",
+ "apihelp-undelete-param-watchlist": "Ajouter ou supprimer la page de la liste de suivi de l’utilisateur actuel sans condition, utiliser les préférences ou ne pas modifier le suivi.",
+ "apihelp-undelete-example-page": "Annuler la suppression de la page <kbd>Main Page</kbd>.",
+ "apihelp-undelete-example-revisions": "Annuler la suppression de deux révisions de la page <kbd>Main Page</kbd>.",
+ "apihelp-upload-description": "Télécharger un fichier, ou obtenir l’état des téléchargements en cours.\n\nPlusieurs méthodes sont disponibles :\n* Télécharger directement le contenu du fichier, en utilisant le paramètre <var>$1file</var>.\n* Télécharger le fichier par morceaux, en utilsiant les paramètres <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var>.* Pour que le serveur MédiaWiki cherche un fichier depuis une URL, utiliser le paramètre <var>$1url</var>.\n* Terminer un téléchargement précédent qui a échoué à cause d’avertissements, en utilisant le paramètre <var>$1filekey</var>.\nNoter que le POST HTTP doit être fait comme un téléchargement de fichier (par ex. en utilisant <code>multipart/form-data</code>) en envoyant le <code>multipart/form-data</code>.",
+ "apihelp-upload-param-filename": "Nom de fichier cible.",
+ "apihelp-upload-param-comment": "Télécharger le commentaire. Utilisé aussi comme texte de la page initiale pour les nouveaux fichiers si <var>$1text</var> n’est pas spécifié.",
+ "apihelp-upload-param-text": "Texte de page initiale pour les nouveaux fichiers.",
+ "apihelp-upload-param-watch": "Suivre la page.",
+ "apihelp-upload-param-watchlist": "Ajouter ou supprimer sans condition la page de la liste de suivi de l’utilisateur actuel, utiliser les préférences ou ne pas changer le suivi.",
+ "apihelp-upload-param-ignorewarnings": "Ignorer tous les avertissements.",
+ "apihelp-upload-param-file": "Contenu du fichier.",
+ "apihelp-upload-param-url": "URL où chercher le fichier.",
+ "apihelp-upload-param-filekey": "Clé identifiant un téléchargement précédent temporairement mis en attente.",
+ "apihelp-upload-param-sessionkey": "Comme $1filekey, conservé pour des raisons de compatibilité descendante.",
+ "apihelp-upload-param-stash": "Si positionné, le serveur conservera temporairement le fichier au lieu de l’ajouter au dépôt.",
+ "apihelp-upload-param-filesize": "Taille du fichier de tout le téléchargement.",
+ "apihelp-upload-param-offset": "Décalage du bloc en octets.",
+ "apihelp-upload-param-chunk": "Partie du contenu.",
+ "apihelp-upload-param-async": "Faire de façon asynchrone les grosses opérations sur les fichiers quand c’est possible.",
+ "apihelp-upload-param-asyncdownload": "Faire de façon asynchrone la recherche d’une URL.",
+ "apihelp-upload-param-leavemessage": "Si asyncdownload est utilisé, laisser un message sur la page de discussion de l’utilisateur quand c’est terminé.",
+ "apihelp-upload-param-statuskey": "Récupérer l’état de téléchargement pour cette clé de fichier (téléchargé par URL).",
+ "apihelp-upload-param-checkstatus": "Récupérer uniquement l’état de téléchargement pour la clé de fichier donnée.",
+ "apihelp-upload-example-url": "Télécharger depuis une URL",
+ "apihelp-upload-example-filekey": "Terminer un téléchargement qui a échoué à cause d’avertissements",
+ "apihelp-userrights-description": "Modifier l’appartenance d’un utilisateur à un groupe.",
+ "apihelp-userrights-param-user": "Nom d’utilisateur.",
+ "apihelp-userrights-param-userid": "ID de l’utilisateur.",
+ "apihelp-userrights-param-add": "Ajouter l’utilisateur à ces groupes.",
+ "apihelp-userrights-param-remove": "Supprimer l’utilisateur de ces groupes.",
+ "apihelp-userrights-param-reason": "Motif pour la modification.",
+ "apihelp-userrights-example-user": "Ajouter l’utilisateur <kbd>FooBot</kbd> au groupe <kbd>robot</kbd>, et le supprimer des groupes <kbd>sysop</kbd> et <kbd>bureaucrate</kbd>.",
+ "apihelp-userrights-example-userid": "Ajouter l’utilisateur d’ID <kbd>123</kbd> au groupe <kbd>robot</kbd>, et le supprimer des groupes <kbd>sysop</kbd> et <kbd>bureaucrate</kbd>.",
+ "apihelp-watch-description": "Ajouter ou supprimer des pages de la liste de suivi de l’utilisateur actuel.",
+ "apihelp-watch-param-title": "La page à (ne plus) suivre. Utiliser plutôt <var>$1titles</var>.",
+ "apihelp-watch-param-unwatch": "Si défini, la page ne sera plus suivie plutôt que suivie.",
+ "apihelp-watch-example-watch": "Suivre la page <kbd>Page principale</kbd>.",
+ "apihelp-watch-example-unwatch": "Ne plus suivre la page <kbd>Page principale</kbd>.",
+ "apihelp-watch-example-generator": "Suivre les quelques premières pages de l’espace de nom principal",
+ "apihelp-format-example-generic": "Mettre en forme le résultat de la requête dans le format $1",
+ "apihelp-dbg-description": "Extraire les données au format de <code>var_export()</code> de PHP.",
+ "apihelp-dbgfm-description": "Extraire les données au format de <code>var_export()</code> de PHP (affiché proprement en HTML).",
+ "apihelp-dump-description": "Extraire les données au format de <code>var_dump()</code> de PHP.",
+ "apihelp-dumpfm-description": "Extraire les données au format de <code>var_dump()</code> de PHP (affiché proprement en HTML).",
+ "apihelp-json-description": "Extraire les données au format JSON.",
+ "apihelp-json-param-callback": "Si spécifié, inclut la sortie dans l’appel d’une fonction fournie. Pour plus de sûreté, toutes les données spécifiques à l’utilisateur seront restreintes.",
+ "apihelp-json-param-utf8": "Si spécifié, encode la plupart (mais pas tous) des caractères non ASCII en URF-8 au lieu de les remplacer par leur séquence d’échappement hexadécimale.",
+ "apihelp-jsonfm-description": "Extraire les données au format JSON (affiché proprement en HTML).",
+ "apihelp-none-description": "Ne rien extraire.",
+ "apihelp-php-description": "Extraire les données au format sérialisé de PHP.",
+ "apihelp-phpfm-description": "Extraire les données au format sérialisé de PHP (affiché proprement en HTML).",
+ "apihelp-rawfm-description": "Extraire les données avec les éléments de débogage au format JSON (affiché proprement en HTML).",
+ "apihelp-txt-description": "Extraire les données au format de <code>print_r()</code> de PHP.",
+ "apihelp-txtfm-description": "Extraire les données au format de <code>print_r()</code> de PHP (affiché proprement en HTML).",
+ "apihelp-wddx-description": "Extraire les données au format WDDX.",
+ "apihelp-wddxfm-description": "Extraire les données au format WDDX (affiché proprement en HTML).",
+ "apihelp-xml-description": "Extraire les données au format XML.",
+ "apihelp-xml-param-xslt": "Si spécifié, ajoute la page nommée comme une feuille de style XSL. La valeur doit être un titre dans l’espace de noms {{ns:mediawiki}} se terminant par <code>.xsl</code>.",
+ "apihelp-xml-param-includexmlnamespace": "Si spécifié, ajoute un espace de noms XML.",
+ "apihelp-xmlfm-description": "Extraire les données au format XML (affiché proprement en HTML).",
+ "apihelp-yaml-description": "Extraire les données au format YAML.",
+ "apihelp-yamlfm-description": "Extraire les données YAML (affiché proprement en HTML).",
+ "api-format-title": "Résultat de l’API de MédiaWiki",
+ "api-format-prettyprint-header": "Voici la représentation HTML du format $1. HTML est utile pour le débogage, mais inapproprié pour être utilisé dans une application.\n\nSpécifiez le paramètre <var>format</var> pour modifier le format de sortie. Pour voir la représentation non HTML du format $1, mettez <kbd>format=$2</kbd>.\n\nVoyez la [[mw:API|documentation complète]], ou l’[[Special:ApiHelp/main|aide de l’API]] pour plus d’information.",
+ "api-orm-param-props": "Champs à rechercher.",
+ "api-orm-param-limit": "Nombre maximal de lignes à renvoyer.",
+ "api-pageset-param-titles": "Une liste des titres sur lesquels travailler.",
+ "api-pageset-param-pageids": "Une liste des IDs de page sur lesquelles travailler.",
+ "api-pageset-param-revids": "Une liste des IDs de révision sur lesquelles travailler.",
+ "api-pageset-param-generator": "Obtenir la liste des pages sur lesquelles travailler en exécutant le module de recherche spécifié.\n\n<strong>NOTE :<strong> les noms de paramètre du générateur doivent être préfixés avec un « g », voir les exemples.",
+ "api-pageset-param-redirects-generator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>, et dans les pages renvoyées par <var>$1generator</var>.",
+ "api-pageset-param-redirects-nogenerator": "Résoudre automatiquement les redirections dans <var>$1titles</var>, <var>$1pageids</var> et <var>$1revids</var>.",
+ "api-pageset-param-converttitles": "Convertir les titres dans d’autres variantes si nécessaire. Fonctionne uniquement si la langue de contenu du wiki supporte la conversion en variantes. Les langues qui supportent la conversion en variante incluent $1.",
+ "api-help-title": "Aide de l’API de MediaWiki",
+ "api-help-lead": "Ceci est une page d’aide de l’API de MédiaWiki générée automatiquement.\n\nDocumentation et exemples : https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "Module principal",
+ "api-help-flag-deprecated": "Ce module est obsolète.",
+ "api-help-flag-internal": "<strong>Ce module est interne ou instable.</strong> Son fonctionnement peut être modifié sans préavis.",
+ "api-help-flag-readrights": "Ce module nécessite des droits de lecture.",
+ "api-help-flag-writerights": "Ce module nécessite des droits d’écriture.",
+ "api-help-flag-mustbeposted": "Ce module n’accepte que les requêtes POST.",
+ "api-help-flag-generator": "Ce module peut être utilisé comme générateur.",
+ "api-help-parameters": "{{PLURAL:$1|Paramètre|Paramètres}} :",
+ "api-help-param-deprecated": "Obsolète.",
+ "api-help-param-required": "Ce paramètre est obligatoire.",
+ "api-help-param-list": "{{PLURAL:$1|1=Une valeur|2=Valeurs (séparées par <kbd>{{!}}</kbd>)}} : $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Doit être vide|Peut être vide, ou $2}}",
+ "api-help-param-limit": "Pas plus de $1 autorisé.",
+ "api-help-param-limit2": "Pas plus de $1 autorisé ($2 pour les robots).",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=La valeur doit être inférieure|2=Les valeurs doivent être inférieures}} à $2.",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=La valeur ne doit pas être supérieure|2=Les valeurs ne doivent pas être supérieures}} à $3.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=La valeur doit|2=Les valeurs doivent}} être entre $2 et $3.",
+ "api-help-param-upload": "Doit être envoyé comme un fichier importé utilisant multipart/form-data.",
+ "api-help-param-multi-separate": "Valeurs séparées par <kbd>|</kbd>.",
+ "api-help-param-multi-max": "Le nombre maximal de valeurs est {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} pour les robots).",
+ "api-help-param-default": "Par défaut : $1",
+ "api-help-param-default-empty": "Par défaut : <span class=\"apihelp-empty\">(vide)</span>",
+ "api-help-param-token": "Un jeton « $1 » récupéré par [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+ "api-help-param-token-webui": "Pour rester compatible, le jeton utilisé dans l’IHM web est aussi accepté.",
+ "api-help-param-disabled-in-miser-mode": "Désactivé à cause du [[mw:Manual:$wgMiserMode|mode minimal]].",
+ "api-help-param-limited-in-miser-mode": "<strong>NOTE :</strong> Du fait du [[mw:Manual:$wgMiserMode|mode minimal]], utiliser cela peut aboutir à moins de résultats que <var>$1limit</var> renvoyés avant de continuer ; dans les cas extrêmes, zéro résultats peuvent être renvoyés.",
+ "api-help-param-direction": "Dans quelle direction énumérer :\n;newer:Lister les plus anciens en premier. Note : $1start doit être avant $1end.\n;older:Lister les nouveaux en premier (par défaut). Note : $1start doit être postérieur à $1end.",
+ "api-help-param-continue": "Quand plus de résultats sont disponibles, utiliser cela pour continuer.",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(aucune description)</span>",
+ "api-help-examples": "{{PLURAL:$1|Exemple|Exemples}} :",
+ "api-help-permissions": "{{PLURAL:$1|Droit|Droits}} :",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Accordé à}} : $2",
+ "api-help-right-apihighlimits": "Utiliser des valeurs plus hautes dans les requêtes de l’API (requêtes lentes : $1 ; requêtes rapides : $2). Les limites pour les requêtes lentes s’appliquent aussi aux paramètres multivalués.",
+ "api-credits-header": "Remerciements",
+ "api-credits": "Développeurs de l’API :\n* Roan Kattouw (développeur en chef Sept. 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (créateur, développeur en chef Sept. 2006–Sept. 2007)\n* Brad Jorsch (développeur en chef depuis 2013)\n\nVeuillez envoyer vos commentaires, suggestions et questions à mediawiki-api@lists.wikimedia.org\nou remplir un rapport de bogue sur https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/frc.json b/includes/api/i18n/frc.json
new file mode 100644
index 00000000..05d5db2d
--- /dev/null
+++ b/includes/api/i18n/frc.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hangmanwa7id"
+ ]
+ },
+ "apihelp-block-description": "Bloquer un useur.",
+ "apihelp-createaccount-param-name": "Nom d'useur.",
+ "apihelp-createaccount-param-password": "Mot de passe (ignoré si <var>$1mailpassword</var> est défini).",
+ "apihelp-createaccount-param-domain": "Domaine pour l’authentification externe (optional).",
+ "apihelp-delete-description": "Effacer une page.",
+ "apihelp-delete-param-title": "Titre de la page que tu veux effacer. Impossible de l’user avec $1pageid.",
+ "apihelp-delete-example-simple": "Effacer la Page principale",
+ "apihelp-emailuser-description": "Emailer un useur.",
+ "apihelp-expandtemplates-param-title": "Titre de la page.",
+ "apihelp-login-param-name": "Nom d’useur.",
+ "apihelp-login-param-password": "Mot de passe.",
+ "apihelp-login-param-domain": "Domaine (optional)."
+}
diff --git a/includes/api/i18n/fy.json b/includes/api/i18n/fy.json
new file mode 100644
index 00000000..05482cfe
--- /dev/null
+++ b/includes/api/i18n/fy.json
@@ -0,0 +1,15 @@
+{
+ "@metadata": {
+ "authors": [
+ "Robin0van0der0vliet"
+ ]
+ },
+ "apihelp-createaccount-param-name": "Brûkersnamme.",
+ "apihelp-login-param-name": "Brûkersnamme.",
+ "apihelp-login-param-password": "Wachtwurd.",
+ "apihelp-userrights-param-user": "Brûkersnamme.",
+ "api-help-param-default": "Standert: $1",
+ "api-help-param-default-empty": "Standert: <span class=\"apihelp-empty\">(leech)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(gjin beskriuwing)</span>",
+ "api-help-examples": "{{PLURAL:$1|Foarbyld|Foarbylden}}:"
+}
diff --git a/includes/api/i18n/gl.json b/includes/api/i18n/gl.json
new file mode 100644
index 00000000..065ced32
--- /dev/null
+++ b/includes/api/i18n/gl.json
@@ -0,0 +1,1030 @@
+{
+ "@metadata": {
+ "authors": [
+ "Elisardojm",
+ "Agremon",
+ "Chairego apc",
+ "VaiPolaSombra",
+ "Banjo",
+ "Fisterraeomar"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentación]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de discusión]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Anuncios da API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Erros e solicitudes]\n</div>\n<strong>Estado:</strong> Tódalas funcionalidades mostradas nesta páxina deberían estar funcionanado, pero a API aínda está desenrolo, e pode ser modificada en calquera momento. Apúntese na [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ lista de discusión mediawiki-api-announce] para estar informado acerca das actualizacións.\n\n<strong>Solicitudes incorrectas:</strong> Cando se envían solicitudes incorrectas á API, envíase unha cabeceira HTTP coa chave \"MediaWiki-API-Error\" e, a seguir, tanto o valor da cabeceira como o código de erro retornado serán definidos co mesmo valor. Para máis información, consulte [[mw:API:Errors_and_warnings|API: Erros e avisos]].",
+ "apihelp-main-param-action": "Que acción se realizará.",
+ "apihelp-main-param-format": "O formato de saída.",
+ "apihelp-main-param-maxlag": "O retardo máximo pode usarse cando MediaWiki está instalada nun cluster de base de datos replicadas. Para gardar accións que causen calquera retardo máis de replicación do sitio, este parámetro pode facer que o cliente espere ata que o retardo de replicación sexa menor que o valor especificado. No caso de retardo excesivo, é devolto o código de erro <samp>maxlag</samp> cunha mensaxe como <samp>esperando por $host: $lag segundos de retardo</samp>.<br />Para máis información, ver [[mw:Manual:Maxlag_parameter|Manual: Maxlag parameter]].",
+ "apihelp-main-param-smaxage": "Fixar a cabeceira <code>s-maxage</code> a esos segundos. Os erros nunca se gardan na caché.",
+ "apihelp-main-param-maxage": "Fixar a cabeceira <code>max-age</code> a esos segundos. Os erros nunca se gardan na caché.",
+ "apihelp-main-param-assert": "Verificar se o usuario está conectado como <kbd>usuario</kbd> ou ten a marca de <kbd>bot</kbd>.",
+ "apihelp-main-param-requestid": "Calquera valor dado aquí será incluído na resposta. Pode usarse para distingir peticións.",
+ "apihelp-main-param-servedby": "Inclúa o nome do servidor que servía a solicitude nos resultados.",
+ "apihelp-main-param-curtimestamp": "Incluir a marca de tempo actual no resultado.",
+ "apihelp-main-param-origin": "Cando se accede á API usando unha petición AJAX entre-dominios (CORS), inicializar o parámetro co dominio orixe. Isto debe incluírse en calquera petición pre-flight, e polo tanto debe ser parte da petición URI (non do corpo POST). Debe coincidir exactamente cunha das orixes na cabeceira <code>Origin</code>, polo que ten que ser fixado a algo como <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Se este parámetro non coincide coa cabeceira <code>Origin</code>, devolverase unha resposta 403. Se este parámetro coincide coa cabeceira <code>Origin</code> e a orixe está na lista branca, porase unha cabeceira <code>Access-Control-Allow-Origin</code>.",
+ "apihelp-main-param-uselang": "Linga a usar para a tradución de mensaxes. Pode consultarse unha lista de códigos en <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd>, ou especificando <kbd>user</kbd> coa preferencia de lingua do usuario actual, ou especificando <kbd>content</kbd> para usar a lingua do contido desta wiki.",
+ "apihelp-block-description": "Bloquear un usuario.",
+ "apihelp-block-param-user": "Nome de usuario, dirección ou rango de IPs que quere bloquear.",
+ "apihelp-block-param-expiry": "Tempo de caducidade. Pode ser relativo (p. ex.<kbd>5 meses</kbd> ou <kbd>2 semanas</kbd>) ou absoluto (p. ex. 2014-09-18T12:34:56Z</kbd>). Se se pon kbd>infinite</kbd>, <kbd>indefinite</kbd>, ou <kbd>never</kbd>, o bloqueo nunca caducará.",
+ "apihelp-block-param-reason": "Motivo para o bloqueo.",
+ "apihelp-block-param-anononly": "Bloquear só usuarios anónimos (é dicir, desactivar edicións anónimas desta dirección IP).",
+ "apihelp-block-param-nocreate": "Previr a creación de contas.",
+ "apihelp-block-param-autoblock": "Bloquear automaticamente o último enderezo IP utilizado, e calquera outro enderezo desde o que intente conectarse.",
+ "apihelp-block-param-noemail": "Impide que o usuario envíe correos electrónicos a través da wiki. (Require o permiso <code>blockemail</code>).",
+ "apihelp-block-param-hidename": "Ocultar o nome de usuario do rexistro de bloqueos. (Precisa do permiso <code>hideuser</code>).",
+ "apihelp-block-param-allowusertalk": "Permitir que o usuario edite a súa propia páxina de conversa (depende de <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "Se o usuario xa está bloqueado, sobreescribir o bloqueo existente.",
+ "apihelp-block-param-watchuser": "Vixiar a páxina de usuario ou direccións IP e a de conversa deste usuario",
+ "apihelp-block-example-ip-simple": "Bloquear dirección IP <kbd>192.0.2.5</kbd> durante tres días coa razón <kbd>Primeiro aviso</kbd>.",
+ "apihelp-block-example-user-complex": "Bloquear indefinidamente ó usuario <kbd>Vándalo</kbd> coa razón <kbd>Vandalismo</kbd>, e impedir a creación de novas contas e envío de correos electrónicos.",
+ "apihelp-checktoken-description": "Verificar a validez dun identificador de <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
+ "apihelp-checktoken-param-type": "Tipo de identificador a probar.",
+ "apihelp-checktoken-param-token": "Símbolo a testar",
+ "apihelp-checktoken-param-maxtokenage": "Tempo máximo autorizado para o identificador, en segundos.",
+ "apihelp-checktoken-example-simple": "Verificar a validez de un identificador <kbd>csrf</kbd>.",
+ "apihelp-clearhasmsg-description": "Limpar a bandeira <code>hasmsg</code> para o usuario actual",
+ "apihelp-clearhasmsg-example-1": "Limpar a bandeira <code>hasmsg</code> para o usuario actual",
+ "apihelp-compare-description": "Obter as diferencias entre dúas páxinas.\n\nDebe indicar un número de revisión, un título de páxina, ou un ID de páxina tanto para \"from\" como para \"to\".",
+ "apihelp-compare-param-fromtitle": "Primeiro título para comparar.",
+ "apihelp-compare-param-fromid": "Identificador da primeira páxina a comparar.",
+ "apihelp-compare-param-fromrev": "Primeira revisión a comparar.",
+ "apihelp-compare-param-totitle": "Segundo título para comparar.",
+ "apihelp-compare-param-toid": "Identificador da segunda páxina a comparar.",
+ "apihelp-compare-param-torev": "Segunda revisión a comparar.",
+ "apihelp-compare-example-1": "Mostrar diferencias entre a revisión 1 e a 2",
+ "apihelp-createaccount-description": "Crear unha nova conta de usuario.",
+ "apihelp-createaccount-param-name": "Nome de usuario.",
+ "apihelp-createaccount-param-password": "Contrasinal (ignorado se <var>$1mailpassword</var> está activo)",
+ "apihelp-createaccount-param-domain": "Dominio para autenticación externa (opcional)",
+ "apihelp-createaccount-param-token": "Símbolo de creación de conta obtido á primeira.",
+ "apihelp-createaccount-param-email": "Enderezo de correo eletrónico do usuario (opcional).",
+ "apihelp-createaccount-param-realname": "Nome real do usuario (opcional).",
+ "apihelp-createaccount-param-mailpassword": "Se se establece calquera valor, enviarase un contrasinal aleatorio ao usuario.",
+ "apihelp-createaccount-param-reason": "Razón opcional de creación da conta para gardar nos rexistros.",
+ "apihelp-createaccount-param-language": "Código de lingua para usar como defecto polo usuario (de xeito opcional, usarase a lingua por defecto)",
+ "apihelp-createaccount-example-pass": "Crear usuario <kbd>testuser</kbd> con contrasinal <kbd>test123</kbd>.",
+ "apihelp-createaccount-example-mail": "Crear usuario <kbd>testmailuser</kbd>\"testmailuser\" e enviar por correo electrónico un contrasinal xenerado de forma aleatoria.",
+ "apihelp-delete-description": "Borrar a páxina.",
+ "apihelp-delete-param-title": "Título da páxina a eliminar. Non pode usarse xunto con <var>$1pageid</var>.",
+ "apihelp-delete-param-pageid": "Identificador da páxina a eliminar. Non pode usarse xunto con <var>$1title</var>.",
+ "apihelp-delete-param-reason": "Razón para o borrado. Se non se indica, usarase unha razón xenerada automaticamente.",
+ "apihelp-delete-param-watch": "Engadir esta páxina á lista de vixilancia do usuario actual.",
+ "apihelp-delete-param-watchlist": "Engadir ou eliminar sen condicións a páxina da lista de vixiancia do usuario actual, use as preferencias ou non cambie a vixiancia.",
+ "apihelp-delete-param-unwatch": "Eliminar esta páxina da lista de vixilancia do usuario actual.",
+ "apihelp-delete-param-oldimage": "Nome da imaxe antiga a borrar como se proporciona en [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]].",
+ "apihelp-delete-example-simple": "Borrar <kbd>Main Page</kbd>.",
+ "apihelp-delete-example-reason": "Eliminar <kbd>Main Page</kbd> coa razón <kbd>Preparing for move</kbd>.",
+ "apihelp-disabled-description": "Este módulo foi desactivado.",
+ "apihelp-edit-description": "Crear e editar páxinas.",
+ "apihelp-edit-param-title": "Título da páxina que quere editar. Non pode usarse xunto con <var>$1pageid</var>.",
+ "apihelp-edit-param-pageid": "Identificador da páxina que quere editar. Non pode usarse xunto con <var>$1title</var>.",
+ "apihelp-edit-param-section": "Número de selección. O <kbd>0</kbd> é para a sección superior, <kbd>new</kbd> para unha sección nova.",
+ "apihelp-edit-param-sectiontitle": "Título para unha nova sección.",
+ "apihelp-edit-param-text": "Contido da páxina.",
+ "apihelp-edit-param-summary": "Resumo de edición. Tamén título de sección cando $1section=new e $1sectiontitle non está definido.",
+ "apihelp-edit-param-minor": "Edición pequena.",
+ "apihelp-edit-param-notminor": "Edición non pequena.",
+ "apihelp-edit-param-bot": "Marcar esta edición como de bot.",
+ "apihelp-edit-param-basetimestamp": "Selo de tempo da revisión de base, usado para detectar conflitos de edición. Pode obterse con [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
+ "apihelp-edit-param-starttimestamp": "Selo de tempo do comezo do proceso de edición, usado para detectar conflitos de edición. Pode obterse un valor axeitado usando <var>[[Special:ApiHelp/main|curtimestamp]]</var> cando se comeza o proceso de edición (p.ex. cando se carga o contido da páxina a editar).",
+ "apihelp-edit-param-recreate": "Ignorar todos os erros da páxina mentres está a ser borrada.",
+ "apihelp-edit-param-createonly": "Non editar a páxina se xa existe.",
+ "apihelp-edit-param-nocreate": "Amosar un mensaxe de erro se a páxina non existe",
+ "apihelp-edit-param-watch": "Engadir esta páxina á lista de vixilancia do usuario actual.",
+ "apihelp-edit-param-unwatch": "Eliminar esta páxina da lista de vixilancia do usuario actual.",
+ "apihelp-edit-param-watchlist": "Engadir ou eliminar sen condicións a páxina da lista de vixiancia do usuario actual, use as preferencias ou non cambie a vixiancia.",
+ "apihelp-edit-param-md5": "A función hash MD5 do parámetro $1text, ou dos parámetros $1prependtext e $1appendtext concatenados. Se está definida, non se fará a edición ata que a función hash sexa correcta.",
+ "apihelp-edit-param-prependtext": "Engadir este texto ao comezo da páxina. Sobreescribirase $1text.",
+ "apihelp-edit-param-appendtext": "Engadir este texto no final da páxina. Ignorar $1text.\n\nUse $1section=new para engadir unha nova sección, máis que este parámetro.",
+ "apihelp-edit-param-undo": "Desfacer esta revisión. Ignorar $1text, $1prependtext e $1appendtext.",
+ "apihelp-edit-param-undoafter": "Desfacer tódalas revisións dende $1undo ata esta. Se non está definido, só desfacer unha revisión.",
+ "apihelp-edit-param-redirect": "Resolver redireccións automáticamente",
+ "apihelp-edit-param-contentformat": "Formato de serialización de contido utilizado para o texto de entrada.",
+ "apihelp-edit-param-contentmodel": "Modelo de contido para o novo contido.",
+ "apihelp-edit-param-token": "O identificador debería enviarse empre como o último parámetro, ou polo menos despois do parámetro $1text.",
+ "apihelp-edit-example-edit": "Editar a páxina",
+ "apihelp-edit-example-prepend": "Antepor <kbd>_&#95;NOTOC_&#95;</kbd> a unha páxina.",
+ "apihelp-edit-example-undo": "Desfacer revisións 13579 a 13585 con resumo automático.",
+ "apihelp-emailuser-description": "Enviar un correo electrónico a un usuario.",
+ "apihelp-emailuser-param-target": "Usuario ó que lle mandar correo electrónico.",
+ "apihelp-emailuser-param-subject": "Asunto.",
+ "apihelp-emailuser-param-text": "Corpo do correo.",
+ "apihelp-emailuser-param-ccme": "Enviarme unha copia deste correo.",
+ "apihelp-emailuser-example-email": "Enviar un correo electrónico ó usuario <kbd>Administrador da wiki</kbd> co texto <kbd>Contido</kbd>.",
+ "apihelp-expandtemplates-description": "Expandir tódolos modelos en wikitexto.",
+ "apihelp-expandtemplates-param-title": "Título da páxina.",
+ "apihelp-expandtemplates-param-text": "Sintaxis wiki a converter.",
+ "apihelp-expandtemplates-param-revid": "ID de revisión, para <nowiki>{{REVISIONID}}</nowiki> e variables similares.",
+ "apihelp-expandtemplates-param-prop": "Pezas de información a retornar:\n;wikitext:O texto wiki expandido.\n;categories:Calquer categoría presente na entrada que non estea representada na saída do texto wiki\n;properties:Propiedades da páxina definidas por palabras máxicas expandidas no texto wiki\n;volatile:Definir se a saída é volátil e se non debe usarse noutra parte da páxina.\n;ttl:Tempo máximo a partir do cal os cachés do resultado deben invalidarse.\n;parsetree:O análise sintáctico en árbore do XML de entrada.\nTeña en conta que se non se selecciona ningún valor o resultado conterá o texto wiki, pero a saída estará nun formato desprezado.",
+ "apihelp-expandtemplates-param-includecomments": "Cando queria incluír comentarios HTML na saída.",
+ "apihelp-expandtemplates-param-generatexml": "Xenerar árbore de análise XML (reemprazado por $1prop=parsetree).",
+ "apihelp-expandtemplates-example-simple": "Expandir o wikitexto <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>.",
+ "apihelp-feedcontributions-description": "Devolve a lista de contribucións dun usuario.",
+ "apihelp-feedcontributions-param-feedformat": "O formato de alimentación.",
+ "apihelp-feedcontributions-param-user": "Para que usuarios recuperar as contribucións.",
+ "apihelp-feedcontributions-param-namespace": "Que espazo de nomes filtrar polas contribucións.",
+ "apihelp-feedcontributions-param-year": "Desde o ano (e anteriores).",
+ "apihelp-feedcontributions-param-month": "Desde o mes de (e anteriores).",
+ "apihelp-feedcontributions-param-tagfilter": "Filtrar as contribucións que teñan estas etiquetas.",
+ "apihelp-feedcontributions-param-deletedonly": "Mostrar só as contribuciones eliminadas.",
+ "apihelp-feedcontributions-param-toponly": "Mostrar só as edicións que que son as ultimas revisións.",
+ "apihelp-feedcontributions-param-newonly": "Mostrar só as edicións que crearon páxinas.",
+ "apihelp-feedcontributions-param-showsizediff": "Mostrar diferenza de tamaño entre edicións.",
+ "apihelp-feedcontributions-example-simple": "Mostrar as contribucións do usuario <kbd>Example</kbd>.",
+ "apihelp-feedrecentchanges-description": "Devolve un ficheiro de cambios recentes.",
+ "apihelp-feedrecentchanges-param-feedformat": "O formato da saída.",
+ "apihelp-feedrecentchanges-param-namespace": "Espazo de nomes ó que limitar os resultados.",
+ "apihelp-feedrecentchanges-param-invert": "Tódolos nomes de espazos agás o seleccionado",
+ "apihelp-feedrecentchanges-param-associated": "Incluir o espazo de nomes asociado (conversa ou principal).",
+ "apihelp-feedrecentchanges-param-days": "Días a limitar os resultados",
+ "apihelp-feedrecentchanges-param-limit": "Número máximo de resultados a visualizar.",
+ "apihelp-feedrecentchanges-param-from": "Mostrar modificacións desde entón.",
+ "apihelp-feedrecentchanges-param-hideminor": "Ocultar cambios menores.",
+ "apihelp-feedrecentchanges-param-hidebots": "Ocultar cambios feitos por bots.",
+ "apihelp-feedrecentchanges-param-hideanons": "Ocultar os cambios realizados por usuarios anónimos.",
+ "apihelp-feedrecentchanges-param-hideliu": "Ocultar os cambios realizados por usuarios rexistrados.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Ocultar os cambios patrullados.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Ocultar os cambios realizados polo usuario actual.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filtrar por etiqueta.",
+ "apihelp-feedrecentchanges-param-target": "Mostrar só os cambios nas páxinas ligadas a esta.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Mostrar os cambios nas páxinas ligadas coa páxina seleccionada.",
+ "apihelp-feedrecentchanges-example-simple": "Mostrar os cambios recentes",
+ "apihelp-feedrecentchanges-example-30days": "Mostrar os cambios recentes limitados a 30 días",
+ "apihelp-feedwatchlist-description": "Devolve o fluxo dunha lista de vixiancia.",
+ "apihelp-feedwatchlist-param-feedformat": "O formato da saída.",
+ "apihelp-feedwatchlist-param-hours": "Lista as páxinas modificadas desde estas horas ata agora.",
+ "apihelp-feedwatchlist-param-linktosections": "Ligar directamente ás seccións modificadas se é posible.",
+ "apihelp-feedwatchlist-example-default": "Mostar o fluxo da lista de vixiancia.",
+ "apihelp-feedwatchlist-example-all6hrs": "Amosar tódolos cambios feitos ás páxinas vixiadas nas últimas 6 horas.",
+ "apihelp-filerevert-description": "Revertir o ficheiro a unha versión anterior.",
+ "apihelp-filerevert-param-filename": "Nome de ficheiro final, sen o prefixo Ficheiro:",
+ "apihelp-filerevert-param-comment": "Comentario de carga.",
+ "apihelp-filerevert-param-archivename": "Nome de ficheiro da revisión á que reverter.",
+ "apihelp-filerevert-example-revert": "Reverter <kbd>Wiki.png</kbd> á versión do <kbd>2011-03-05T15:27:40Z</kbd>.",
+ "apihelp-help-description": "Mostrar axuda para os módulos indicados.",
+ "apihelp-help-param-modules": "Módulos para mostar axuda (valores dos parámetros <var>acción</var> e <var>formato</var>, ou <kbd>principal</kbd>). Pode especificar submódulos con un <kbd>+</kbd>.",
+ "apihelp-help-param-submodules": "Incluír axuda para os submódulos do módulo nomeado.",
+ "apihelp-help-param-recursivesubmodules": "Incluír axuda para os submódulos de forma recursiva.",
+ "apihelp-help-param-helpformat": "Formato de saída da axuda.",
+ "apihelp-help-param-wrap": "Incluír a saída nunha estrutura de resposta API estándar.",
+ "apihelp-help-param-toc": "Incluír unha táboa de contidos na saída por HTML",
+ "apihelp-help-example-main": "Axuda para o módulo principal",
+ "apihelp-help-example-recursive": "Toda a axuda nunha páxina",
+ "apihelp-help-example-help": "Axuda do módulo de axuda en si",
+ "apihelp-help-example-query": "Axuda para dous submódulos de consulta.",
+ "apihelp-imagerotate-description": "Xirar unha ou máis imaxes.",
+ "apihelp-imagerotate-param-rotation": "Graos a rotar a imaxe no sentido do reloxio.",
+ "apihelp-imagerotate-example-simple": "Rotar <kbd>File:Example.png</kbd> <kbd>90</kbd> graos.",
+ "apihelp-imagerotate-example-generator": "Rotar tódalas imaxes en <kbd>Category:Flip</kbd> <kbd>180</kbd> graos",
+ "apihelp-import-description": "Importar unha páxina doutra wiki, ou nun ficheiro XML.\n\nDecátese de que o POST HTTP debe facerse como unha carga de ficheiro (p. ex. usando multipart/form-data) cando se envíe un ficheiro para o parámetro <var>xml</var>.",
+ "apihelp-import-param-summary": "Resume de importación.",
+ "apihelp-import-param-xml": "Subido ficheiro XML.",
+ "apihelp-import-param-interwikisource": "Para importacións interwiki: wiki da que importar.",
+ "apihelp-import-param-interwikipage": "Para importacións interwiki: páxina a importar.",
+ "apihelp-import-param-fullhistory": "Para importacións interwiki: importar o historial completo, non só a versión actual.",
+ "apihelp-import-param-templates": "Para importacións interwiki: importar tódolos modelos incluídos.",
+ "apihelp-import-param-namespace": "Para importacións interwiki: importar a este espazo de nomes.",
+ "apihelp-import-param-rootpage": "Importar como subpáxina desta páxina.",
+ "apihelp-import-example-import": "Importar [[meta:Help:Parserfunctions]] ó espazo de nomes 100 con todo o historial.",
+ "apihelp-login-description": "No caso dunha conexión correcta, as cookies necesarias incluiranse nas cabeceiras HTTP de resposta. No caso dunha conexión fallida, os intentos posteriores poden ser reducidos para limitar ataques automaticos de roubo de contrasinais.",
+ "apihelp-login-param-name": "Nome de usuario.",
+ "apihelp-login-param-password": "Contrasinal",
+ "apihelp-login-param-domain": "Dominio (opcional).",
+ "apihelp-login-param-token": "Identificador de conexión obtido na primeira petición.",
+ "apihelp-login-example-gettoken": "Recuperar un identificador de conexión.",
+ "apihelp-login-example-login": "Identificarse",
+ "apihelp-logout-description": "Terminar e limpar datos de sesión.",
+ "apihelp-logout-example-logout": "Cerrar a sesión do usuario actual",
+ "apihelp-managetags-description": "Realizar tarefas de xestión relacionadas coa modificación de etiquetas.",
+ "apihelp-managetags-param-operation": "Que operación realizar:\n;create:Crear unha nova etiqueta de modificación para uso manual.\n;delete:Borar unha etiqueta de modificación da base de datos, incluíndo o borrado da etiqueta de todas as revisións, entradas de cambios recentes e entradas de rexistro onde estea a usarse.\n;activate:Activar unha etiqueta de modificación, permitindo que os usuarios a usen manualmente.\n;deactivate:Desactivar unha etiqueta de modificación, impedindo que os usuarios a usen manualmente.",
+ "apihelp-managetags-param-tag": "Etiqueta para crear, borrar, activar ou desactivar. Para a creación da etiqueta, a etiqueta non pode existir previamente. Para o borrado da etiqueta, a etiqueta debe existir. Para a activación da etiqueta, a etiqueta debe existir e non pode ser usada por unha extensión. Para desactivar unha etiqueta, a etiqueta debe estar activa e definida manualmente.",
+ "apihelp-managetags-param-reason": "Un motivo opcional para crear, borrar, activar ou desactivar a etiqueta.",
+ "apihelp-managetags-param-ignorewarnings": "Ignorar calquera aviso que apareza durante a operación.",
+ "apihelp-managetags-example-create": "Crear unha etiqueta chamada <kbd>publicidade</kbd> coa razón <kbd>Para usar en vixiancia de edicións</kbd>",
+ "apihelp-managetags-example-delete": "Borrar a etiqueta <kbd>vandalismo</kbd> coa razón <kbd>Erros ortográficos</kbd>",
+ "apihelp-managetags-example-activate": "Activar a etiqueta chamada <kbd>publicidade</kbd> coa razón <kbd>Para usar en vixiancia de edicións</kbd>",
+ "apihelp-managetags-example-deactivate": "Desactivar a etiqueta chamada <kbd>publicidade</kbd> coa razón <kbd>Xa non é necesaria</kbd>",
+ "apihelp-move-description": "Mover unha páxina.",
+ "apihelp-move-param-from": "Título da páxina que quere renomear. Non pode usarse xunto con <var>$1fromid</var>.",
+ "apihelp-move-param-fromid": "Identificador da páxina que quere renomear. Non pode usarse xunto con <var>$1from</var>.",
+ "apihelp-move-param-to": "Título ó que renomear a páxina.",
+ "apihelp-move-param-reason": "Motivo para o renomeamento.",
+ "apihelp-move-param-movetalk": "Renomear a páxina de conversa, se existe.",
+ "apihelp-move-param-movesubpages": "Renomear as subpáxinas, se é aplicable.",
+ "apihelp-move-param-noredirect": "Non crear unha redirección.",
+ "apihelp-move-param-watch": "Engadir a páxina e a redirección á páxina de vixiancia do usuario actual.",
+ "apihelp-move-param-unwatch": "Eliminar a páxina e a redirección da páxina de vixiancia do usuario actual.",
+ "apihelp-move-param-watchlist": "Engadir ou eliminar sen condicións a páxina da lista de vixiancia do usuario actual, use as preferencias ou non cambie a vixiancia.",
+ "apihelp-move-param-ignorewarnings": "Ignorar as advertencias.",
+ "apihelp-move-example-move": "Mover <kbd>Títulomalo</kbd> a <kbd>Títulobo</kbd> sen deixar unha redirección.",
+ "apihelp-opensearch-description": "Buscar no wiki mediante o protocolo OpenSearch.",
+ "apihelp-opensearch-param-search": "Buscar texto.",
+ "apihelp-opensearch-param-limit": "Número máximo de resultados a visualizar.",
+ "apihelp-opensearch-param-namespace": "Espazo de nomes no que buscar.",
+ "apihelp-opensearch-param-suggest": "Non facer nada se <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> é falso.",
+ "apihelp-opensearch-param-redirects": "Como xestionar as redireccións:\n;return:Devolve a mesma redirección.\n;resolve:Devolve a páxina á que apunta. Pode devolver menos de $1limit resultados.\nPor razóns históricas, o valor por defecto para $1format=json é \"return\" e \"resolve\" para outros formatos.",
+ "apihelp-opensearch-param-format": "O formato de saída.",
+ "apihelp-opensearch-example-te": "Atopar páxinas que comezan por <kbd>Te</kbd>.",
+ "apihelp-options-description": "Cambiar as preferencias do usuario actual.\n\nSó se poden cambiar opcións que estean rexistradas no núcleo ou nunha das extensións instaladas, ou opcións con claves prefixadas con \"userjs-\" (previstas para ser usadas por scripts de usuario).",
+ "apihelp-options-param-reset": "Reiniciar preferencias ás iniciais do sitio.",
+ "apihelp-options-param-resetkinds": "Lista de tipos de opcións a reinicializar cando a opción <var>$1reset</var> está definida.",
+ "apihelp-options-param-change": "Lista de cambios, con formato nome=valor (p. ex. skin=vector). O valor non pode ter caracteres de barra vertical. Se non se indica un valor (sen u signo igual), p. ex. nomeopcion|outraopcion|..., a opción será gardada co seu valor por defecto.",
+ "apihelp-options-param-optionname": "Nome dunha opción que debe ser fixado ó valor dado por <var>$1optionvalue</var>.",
+ "apihelp-options-param-optionvalue": "Valor da opción especificada por <var>$1optionname</var>, pode conter o caracter da barra vertical.",
+ "apihelp-options-example-reset": "Restablecer tódaalas preferencias",
+ "apihelp-options-example-change": "Cambiar as preferencias <kbd>skin</kbd> and <kbd>hideminor</kbd>.",
+ "apihelp-options-example-complex": "Restaurar todas as preferencias, logo fixar <kbd>skin</kbd> e <kbd>nickname</kbd>.",
+ "apihelp-paraminfo-description": "Obter información sobre módulos API.",
+ "apihelp-paraminfo-param-modules": "Lista de nomes de módulos (valores dos parámetros <var>acción</var e <var>formato</var>, ou <kbd>principal</kbd>). Pode especificar submódulos con <kbd>+</kbd>.",
+ "apihelp-paraminfo-param-helpformat": "Formato das cadeas de axuda.",
+ "apihelp-paraminfo-param-querymodules": "Lista dos nomes de módulos de consulta (valores dos parámetros <var>prop</var>, <var>meta</var> ou <var>list</var>). Use <kbd>$1modules=query+foo</kbd> no canto de <kbd>$1querymodules=foo</kbd>.",
+ "apihelp-paraminfo-param-mainmodule": "Obter información sobre o módulo principal (nivel superior). No canto use <kbd>$1modules=main</kbd>.",
+ "apihelp-paraminfo-param-pagesetmodule": "Obter información sobre o módulo pageset (proporcionando títulos= e amigos).",
+ "apihelp-paraminfo-param-formatmodules": "Lista dos nomes de módulo de formato (valores do parámetro <var>formato</var>). No canto use <var>$1modules</var>.",
+ "apihelp-paraminfo-example-1": "Amosar información para <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, e <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+ "apihelp-parse-param-title": "Título da páxina á que pertence o texto. Se non se indica, debe especificarse <var>$1contentmodel</var>, e [[API]] usarase como o título.",
+ "apihelp-parse-param-text": "Texto a analizar. Use <var>$1title</var> ou <var>$1contentmodel</var> para controlar o modelo de contido.",
+ "apihelp-parse-param-summary": "Resumo a analizar.",
+ "apihelp-parse-param-page": "Analizar o contido desta páxina. Non pode usarse de forma conxunta con <var>$1text</var> e <var>$1title</var>.",
+ "apihelp-parse-param-pageid": "Analizar o contido desta páxina. Ignora <var>$1page</var>.",
+ "apihelp-parse-param-redirects": "Se <var>$1page</var> ou <var>$1pageid</var> apuntar a unha redirección, resólvea.",
+ "apihelp-parse-param-oldid": "Analizar o contido desta revisión. Ignora <var>$1page</var> e <var>$1pageid</var>.",
+ "apihelp-parse-param-pst": "Fai unha transformación antes de gardar a entrada antes de analizala. Válida unicamente para usar con texto.",
+ "apihelp-parse-param-onlypst": "Facer unha transformación antes de gardar (PST) a entrada, pero sen analizala. Devolve o mesmo wikitexto, despois de que a PST foi aplicada. Só válida cando se usa con <var>$1text</var>.",
+ "apihelp-parse-param-effectivelanglinks": "Inclúe ligazóns de idioma proporcionadas polas extensións (para usar con <kbd>$1prop=langlinks</kbd>).",
+ "apihelp-parse-param-section": "Recuperar unicamente o contido deste número de sección ou cando <kbd>new</kbd> xera unha nova sección.\n\nA sección <kbd>new</kbd> só é atendida cando se especifica <var>text</var>.",
+ "apihelp-parse-param-sectiontitle": "Novo título de sección cando <var>section</var> é <kbd>new</kbd>.\n\nA diferenza da edición de páxinas, non se oculta no <var>summary</var> cando se omite ou está baleiro.",
+ "apihelp-parse-param-disablepp": "Desactivar o informe PP da saída do analizador.",
+ "apihelp-parse-param-disableeditsection": "Desactivar as ligazóns de edición de sección da saída do analizador.",
+ "apihelp-parse-param-generatexml": "Xenerar unha árbore de análise XML (necesita o modelo de contido <code>$1</code>).",
+ "apihelp-parse-param-preview": "Analizar en modo vista previa.",
+ "apihelp-parse-param-sectionpreview": "Analizar en modo vista previa de sección (activa tamén o modo de vista previa).",
+ "apihelp-parse-param-disabletoc": "Desactiva o índice na saída.",
+ "apihelp-parse-param-contentformat": "Formato de serialización do contido usado para o texto de entrada. Só válido cando se usa con $1text.",
+ "apihelp-parse-param-contentmodel": "Modelo de contido do texto de entrada. Se se omite, debe especificarse $1title, e o valor por defecto será o modelo do título especificado. Só válido cando se usa con $1text.",
+ "apihelp-parse-example-page": "Analizar unha páxina.",
+ "apihelp-parse-example-text": "Analizar un wikitexto.",
+ "apihelp-parse-example-texttitle": "Analizar wikitexto, especificando o título da páxina.",
+ "apihelp-parse-example-summary": "Analizar un resumo.",
+ "apihelp-patrol-description": "Patrullar unha páxina ou edición.",
+ "apihelp-patrol-param-rcid": "ID de modificación recente a vixiar.",
+ "apihelp-patrol-param-revid": "ID de revisión a vixiar.",
+ "apihelp-patrol-example-rcid": "Patrullar un cambio recente",
+ "apihelp-patrol-example-revid": "Patrullar unha revisión",
+ "apihelp-protect-description": "Cambiar o nivel de protección dunha páxina.",
+ "apihelp-protect-param-title": "Título da páxina que quere (des)protexer. Non pode usarse xunto con $1pageid.",
+ "apihelp-protect-param-pageid": "Identificador da páxina que quere (des)protexer. Non pode usarse xunto con $1title.",
+ "apihelp-protect-param-protections": "Lista dos niveis de protección, con formato <kbd>action=level</kbd> (p.ex. <kbd>edit=sysop</kbd>).\n\n<strong>Nota:</strong> Todas as accións que non estean listadas terán restriccións para ser eliminadas.",
+ "apihelp-protect-param-expiry": "Selos de tempo de caducidade. Se só se indica un selo de tempo, usarase para todas as proteccións. Use <kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd>, ou <kbd>never</kbd>, para unha protección sen caducidade.",
+ "apihelp-protect-param-reason": "Razón para (des)protexer.",
+ "apihelp-protect-param-cascade": "Activar protección en cascada (p. ex. protexer páxinas incluídas nesta páxina). Ignorado se todos os niveis de protección proporcionados non permiten o uso en cascada.",
+ "apihelp-protect-param-watch": "Se se define este parámetro, engadir a páxina que se (des)protexe á lista de vixilancia do usuario actual.",
+ "apihelp-protect-param-watchlist": "Engadir ou eliminar sen condicións a páxina da lista de vixiancia do usuario actual, use as preferencias ou non cambie a vixiancia.",
+ "apihelp-protect-example-protect": "Protexer unha páxina",
+ "apihelp-protect-example-unprotect": "Desprotexer unha páxina poñendo as restricións a <kbd>all</kbd>.",
+ "apihelp-protect-example-unprotect2": "Desprotexer unha páxina quitando as restricións.",
+ "apihelp-purge-description": "Borrar a caché para os títulos indicados.\n\nPrecisa dunha petición POST se o usuario non está conectado.",
+ "apihelp-purge-param-forcelinkupdate": "Actualizar as táboas de ligazóns.",
+ "apihelp-purge-param-forcerecursivelinkupdate": "Actualizar a táboa de ligazóns, e actualizar as táboas de ligazóns para calquera páxina que use esta páxina como modelo.",
+ "apihelp-purge-example-simple": "Purgar a <kbd>Páxina Principal</kbd> e páxina da <kbd>API</kbd>.",
+ "apihelp-purge-example-generator": "Purgar as primeiras 10 páxinas no espazo de nomes principal.",
+ "apihelp-query-description": "Consultar datos de e sobre MediaWiki.\n\nTodas as modificacións de datos primeiro teñen que facer unha busca para obter un identificador para evitar abusos de sitios maliciosos.",
+ "apihelp-query-param-prop": "Que propiedades obter para as páxinas buscadas.",
+ "apihelp-query-param-list": "Que lista obter.",
+ "apihelp-query-param-meta": "Que metadatos obter.",
+ "apihelp-query-param-indexpageids": "Incluir una sección adicional de identificadores de páxina listando todos os IDs das páxinas devoltas.",
+ "apihelp-query-param-export": "Exportar as revisións actuais de todas as páxinas dadas ou xeneradas.",
+ "apihelp-query-param-exportnowrap": "Devolver o XML exportado sen incluílo nun resultado XML (mesmo formato que [[Special:Export]]). Só pode usarse con $1export.",
+ "apihelp-query-param-iwurl": "Se fai falta obter a URL completa se o título é unha ligazón interwiki.",
+ "apihelp-query-param-continue": "Cando está presente, formatea query-continue como pares clave-valor que simplemente serán mesturados na consulta orixinal. Este parámetro debe fixarse a unha cadea baleira na consulta inicial.\n\nEste parámetro está recomendado para todos os novos desenvolvementos, e será o usado por defecto na seguinte versión da API.",
+ "apihelp-query-param-rawcontinue": "Actualmente ignorado. No futuro, <var>$1continue</var> virá por defecto e será necesario para recibir os datos en bruto de <samp>query-continue</samp>.",
+ "apihelp-query-example-revisions": "Consultar [[Special:ApiHelp/query+siteinfo|información do sitio]] e [[Special:ApiHelp/query+revisions|as revisións]] da <kbd>Páxina Principal</kbd>.",
+ "apihelp-query-example-allpages": "Buscar revisións de páxinas que comecen por <kbd>API/</kbd>.",
+ "apihelp-query+allcategories-description": "Numerar tódalas categorías",
+ "apihelp-query+allcategories-param-from": "Categoría pola que comezar a enumeración.",
+ "apihelp-query+allcategories-param-to": "Categoría pola que rematar a enumeración.",
+ "apihelp-query+allcategories-param-prefix": "Buscar todos os títulos de categoría que comezan con este valor.",
+ "apihelp-query+allcategories-param-dir": "Dirección na que ordenar.",
+ "apihelp-query+allcategories-param-min": "Devolver só categorías con polo menos este número de membros.",
+ "apihelp-query+allcategories-param-max": "Devolver só categorías con como moito este número de membros.",
+ "apihelp-query+allcategories-param-limit": "Cantas categorías devolver.",
+ "apihelp-query+allcategories-param-prop": "Que propiedades recuperar:\n;size: Engade o número de páxinas na categoría.\n;hidden: Marca as categorías que están ocultas con _&#95;HIDDENCAT_&#95;.",
+ "apihelp-query+allcategories-example-size": "Listar categorías con información do número de páxinas en cada unha.",
+ "apihelp-query+allcategories-example-generator": "Obter información sobre a páxina de categoría para categorías que comezan por <kbd>List</kbd>.",
+ "apihelp-query+alldeletedrevisions-description": "Listar todas as revisións borradas por un usuario ou nun espazo de nomes.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Só pode usarse con <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Non pode usarse con <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-param-start": "Selo de tempo para comezar a enumeración.",
+ "apihelp-query+alldeletedrevisions-param-end": "Selo de tempo para rematar a enumeración.",
+ "apihelp-query+alldeletedrevisions-param-from": "Comezar listado neste título.",
+ "apihelp-query+alldeletedrevisions-param-to": "Parar listado neste título.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Buscar tódolos títulos de páxinas que comezan con este valor.",
+ "apihelp-query+alldeletedrevisions-param-tag": "Só listar revisións marcadas con esta etiqueta.",
+ "apihelp-query+alldeletedrevisions-param-user": "Só listar revisións deste usuario.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Non listar revisións deste usuario.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Só listar páxinas neste espazo de nomes.",
+ "apihelp-query+alldeletedrevisions-param-miser-user-namespace": "<strong>Nota:</strong> Debido ó [[mw:Manual:$wgMiserMode|modo minimal]], ó usar á vez <var>$1user</var> e <var>$1namespace</var> pode devolver menos resultados de <var>$1limit</var> antes de continuar, en casos extremos, pode que non devolva resultados.",
+ "apihelp-query+alldeletedrevisions-param-generatetitles": "Usado como xenerador, xenera títulos no canto de IDs de revisión.",
+ "apihelp-query+alldeletedrevisions-example-user": "Listar as últimas 50 contribucións borradas do usuario <kbd>Exemplo<kbd>.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Listar as 50 primeiras revisións borradas no espazo de nomes principal.",
+ "apihelp-query+allfileusages-description": "Lista todos os usos de ficheiro, incluído os que non existen.",
+ "apihelp-query+allfileusages-param-from": "Título do ficheiro no que comezar a enumerar.",
+ "apihelp-query+allfileusages-param-to": "Título do ficheiro no que rematar de enumerar.",
+ "apihelp-query+allfileusages-param-prefix": "Buscar tódolos títulos de ficheiro que comezan con este valor.",
+ "apihelp-query+allfileusages-param-unique": "Mostrar só nomes de ficheiro distintos. Non pode usarse con $1prop=ids.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.",
+ "apihelp-query+allfileusages-param-prop": "Que partes de información incluír:\n;ids:Engade o ID de páxina usada (non pode usarse con $1unique).\n;title:Engade o nome do ficheiro.",
+ "apihelp-query+allfileusages-param-limit": "Número total de obxectos a devolver.",
+ "apihelp-query+allfileusages-param-dir": "Dirección na cal listar.",
+ "apihelp-query+allfileusages-example-B": "Lista títulos de ficheiro, incluíndo os eliminados, cos IDs de páxina dos que proveñen, comezando en <kbd>B</kbd>.",
+ "apihelp-query+allfileusages-example-unique": "Listar títulos únicos de ficheiros.",
+ "apihelp-query+allfileusages-example-unique-generator": "Obter todos os títulos de ficheiro, marcando os eliminados.",
+ "apihelp-query+allfileusages-example-generator": "Obtén as páxinas que conteñen os ficheiros.",
+ "apihelp-query+allimages-description": "Enumerar tódalas imaxes secuencialmente.",
+ "apihelp-query+allimages-param-sort": "Propiedade pola que ordenar.",
+ "apihelp-query+allimages-param-dir": "Dirección na cal listar.",
+ "apihelp-query+allimages-param-from": "Título da imaxe no que comezar a enumerar. Só pode usarse con $1sort=name.",
+ "apihelp-query+allimages-param-to": "Título da imaxe no que rematar a enumerar. Só pode usarse con $1sort=name.",
+ "apihelp-query+allimages-param-start": "Título do selo de tempo no que comezar a enumerar. Só pode usarse con $1sort=timestamp.",
+ "apihelp-query+allimages-param-end": "Título do selo de tempo no que rematar a enumerar. Só pode usarse con $1sort=timestamp.",
+ "apihelp-query+allimages-param-prefix": "Buscar todas as imaxes cuxo título comeza por este valor. Só pode usarse con $1sort=name.",
+ "apihelp-query+allimages-param-minsize": "Limitar a imaxes con polo menos este número de bytes.",
+ "apihelp-query+allimages-param-maxsize": "Limitar a imaxes con como máximo este número de bytes.",
+ "apihelp-query+allimages-param-sha1": "Función hash SHA1 da imaxe. Invalida $1sha1base36.",
+ "apihelp-query+allimages-param-sha1base36": "Función hash SHA1 da imaxe en base 36 (usada en MediaWiki).",
+ "apihelp-query+allimages-param-user": "Mostrar só ficheiros subidos por este usuario. Só pode usarse con $1sort=timestamp. Non se pode usar xunto a $1filterbots.",
+ "apihelp-query+allimages-param-filterbots": "Como filtrar ficheiros subidos por bots. Só pode usarse con $1sort=timestamp. Non pode usarse xunto con $1user.",
+ "apihelp-query+allimages-param-mime": "Que tipos MIME buscar, por exemplo <kbd>imaxe/jpeg</kbd>.",
+ "apihelp-query+allimages-param-limit": "Cantas imaxes mostar en total.",
+ "apihelp-query+allimages-example-B": "Mostrar unha lista de ficheiros que comezan por <kbd>B</kbd>.",
+ "apihelp-query+allimages-example-recent": "Mostrar unha lista de ficheiros subidos recentemente, similares a [[Special:NewFiles]].",
+ "apihelp-query+allimages-example-mimetypes": "Mostrar unha lista de ficheiros con tipo MIME <kbd>image/png</kbd> ou <kbd>image/gif</kbd>",
+ "apihelp-query+allimages-example-generator": "Mostar información sobre catro ficheiros que comecen pola letra <kbd>T</kbd>.",
+ "apihelp-query+alllinks-description": "Numerar tódalas ligazóns que apuntan a un nome de espazos determinado.",
+ "apihelp-query+alllinks-param-from": "Título da ligazón na que comezar a enumerar.",
+ "apihelp-query+alllinks-param-to": "Título da ligazón na que rematar de enumerar.",
+ "apihelp-query+alllinks-param-prefix": "Buscar tódolos títulos ligados que comezan con este valor.",
+ "apihelp-query+alllinks-param-unique": "Mostrar só títulos ligados distintos. Non pode usarse con <kbd>$1prop=ids</kbd>.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.",
+ "apihelp-query+alllinks-param-prop": "Que partes de información incluír:\n;ids: Engade o ID da páxina da ligazón (non pode usarse con <var>$1unique</var>).\n;título: Engade o título da ligazón.",
+ "apihelp-query+alllinks-param-namespace": "Espazo de nomes a enumerar.",
+ "apihelp-query+alllinks-param-limit": "Número total de obxectos a devolver.",
+ "apihelp-query+alllinks-param-dir": "Dirección na cal listar.",
+ "apihelp-query+alllinks-example-B": "Lista os títulos ligados, incluíndo os eliminados, cos ID das páxinas das que proveñen, comezando en <kbd>B</kbd>.",
+ "apihelp-query+alllinks-example-unique": "Listar títulos ligados únicos",
+ "apihelp-query+alllinks-example-unique-generator": "Obtén tódolos títulos ligados, marcando os eliminados.",
+ "apihelp-query+alllinks-example-generator": "Obtén as páxinas que conteñen as ligazóns.",
+ "apihelp-query+allmessages-description": "Devolver mensaxes deste sitio.",
+ "apihelp-query+allmessages-param-messages": "Que mensaxes devolver. <kbd>*</kbd> (por defecto) significa todas as mensaxes",
+ "apihelp-query+allmessages-param-prop": "Que propiedades obter.",
+ "apihelp-query+allmessages-param-enableparser": "Marcar para activar o analizador, isto preprocesará o texto wiki da mensaxe (substituir palabras máxicas, xestionar modelo, etc.)",
+ "apihelp-query+allmessages-param-nocontent": "Se se marca, non inclúe o contido das mensaxes na saída.",
+ "apihelp-query+allmessages-param-includelocal": "Tamén inclúe mensaxes locais, p.ex. mensaxes que non existen no software pero existen como unha páxina MediaWiki:. \nIsto lista todas as páxinas MediaWiki:, polo que tamén listará as que non son realmente mensaxes como [[MediaWiki:Common.js|Common.js]].",
+ "apihelp-query+allmessages-param-args": "Argumentos a substituír na mensaxe.",
+ "apihelp-query+allmessages-param-filter": "Retornar só mensaxes con nomes que conteñan esta cadea.",
+ "apihelp-query+allmessages-param-customised": "Devolver só mensaxes neste estado de personalización.",
+ "apihelp-query+allmessages-param-lang": "Retornar mensaxes nesta lingua.",
+ "apihelp-query+allmessages-param-from": "Retornar mensaxes que comezan nesta mensaxe.",
+ "apihelp-query+allmessages-param-to": "Retornar mensaxes que rematan nesta mensaxe.",
+ "apihelp-query+allmessages-param-title": "Nome de páxina a usar como contexto cando se analice a mensaxe (para a opción $1enableparser)",
+ "apihelp-query+allmessages-param-prefix": "Devolver mensaxes con este prefixo.",
+ "apihelp-query+allmessages-example-ipb": "Mostar mensaxes que comecen por <kbd>ipb-</kbd>.",
+ "apihelp-query+allmessages-example-de": "Mostrar mensaxes <kbd>august</kbd> e <kbd>mainpage</kbd> en Alemán.",
+ "apihelp-query+allpages-description": "Numerar tódalas páxinas secuencialmente nun espazo de nomes determinado.",
+ "apihelp-query+allpages-param-from": "Título da páxina na que comezar a enumerar.",
+ "apihelp-query+allpages-param-to": "Título da páxina na que rematar de enumerar.",
+ "apihelp-query+allpages-param-prefix": "Buscar tódolos títulos de páxinas que comezan con este valor.",
+ "apihelp-query+allpages-param-namespace": "Espazo de nomes a enumerar.",
+ "apihelp-query+allpages-param-filterredir": "Que páxinas listar.",
+ "apihelp-query+allpages-param-minsize": "Limitar a páxinas con polo menos este número de bytes.",
+ "apihelp-query+allpages-param-maxsize": "Limitar a páxinas con como máximo este número de bytes.",
+ "apihelp-query+allpages-param-prtype": "Limitar a só protección de páxinas.",
+ "apihelp-query+allpages-param-prlevel": "Filtrar proteccións baseándose no nivel de protección (debe empregarse có parámetro $1prtype= ).",
+ "apihelp-query+allpages-param-prfiltercascade": "Filtrar proteccións baseadas en cascada (ignoradas se $1prtype non ten valor).",
+ "apihelp-query+allpages-param-limit": "Número total de páxinas a devolver.",
+ "apihelp-query+allpages-param-dir": "Dirección na cal listar.",
+ "apihelp-query+allpages-param-filterlanglinks": "Filtro baseado en si unha páxina ten ligazóns de lingua. Decátese de que esto pode non considerar as ligazóns de lingua engadidas polas extensións.",
+ "apihelp-query+allpages-param-prexpiry": "Que finalización de protección pola que filtrar a páxina:\n;indefinida: Só obter páxinas coa finalización de protección indefinida.\n;definite: Só obter páxinas cunha finalización de protección definida.\n;all: Obter páxinas con calquera finalización de protección.",
+ "apihelp-query+allpages-example-B": "Mostrar unha lista de páxinas que comezan pola letra <kbd>B</kbd>.",
+ "apihelp-query+allpages-example-generator": "Mostrar inforfmación sobre 4 páxinas que comecen pola letra <kbd>T</kbd>.",
+ "apihelp-query+allpages-example-generator-revisions": "Motrar o contido das dúas primeiras páxinas que non sexan redirección que comecen por <kbd>Re</kbd>.",
+ "apihelp-query+allredirects-description": "Lista tódalas redireccións a un espazo de nomes.",
+ "apihelp-query+allredirects-param-from": "Título da redirección na que comezar a enumerar.",
+ "apihelp-query+allredirects-param-to": "Título da redirección na que rematar de enumerar.",
+ "apihelp-query+allredirects-param-prefix": "Buscar todas as páxinas que comecen con este valor.",
+ "apihelp-query+allredirects-param-unique": "Só mostrar páxinas obxectivo distintas. Non pode usarse con $1prop=ids|fragment|interwiki.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.",
+ "apihelp-query+allredirects-param-prop": "Que información incluír:\n;ids:Engade o ID da páxina da redirección (non pode usarse con <var>$1unique</var>).\n;title:Engade o título da redirección.\n;fragment:Engade o fragmento da redirección, se o hai (non pode usarse con <var>$1unique</var>).\n;interwiki:Engade o prefixo interwiki da redirección, se o hai (non pode usarse con <var>$1unique</var>).",
+ "apihelp-query+allredirects-param-namespace": "Espazo de nomes a enumerar.",
+ "apihelp-query+allredirects-param-limit": "Número total de obxectos a devolver.",
+ "apihelp-query+allredirects-param-dir": "Dirección na cal listar.",
+ "apihelp-query+allredirects-example-B": "Lista as páxinas obxectivo, incluíndo as eliminadas, cos ID das páxinas das que proveñen, comezando en <kbd>B</kbd>.",
+ "apihelp-query+allredirects-example-unique": "Lista páxinas obxectivo únicas.",
+ "apihelp-query+allredirects-example-unique-generator": "Obtén tódalas páxinas obxectivo, marcando as eliminadas.",
+ "apihelp-query+allredirects-example-generator": "Obtén as páxinas que conteñen as redireccións.",
+ "apihelp-query+alltransclusions-description": "Listar todas as transclusións (páxinas integradas usando &#123;&#123;x&#125;&#125;), incluíndo as eliminadas.",
+ "apihelp-query+alltransclusions-param-from": "Título da transclusión na que comezar a enumerar.",
+ "apihelp-query+alltransclusions-param-to": "Título da transclusión na que rematar de enumerar.",
+ "apihelp-query+alltransclusions-param-prefix": "Buscar todos os títulos transcluídos que comezan con este valor.",
+ "apihelp-query+alltransclusions-param-unique": "Mostrar só títulos transcluídos distintos. Non pode usarse con <kbd>$1prop=ids</kbd>.\nCando se usa como xenerador, produce páxinas obxectivo no canto de páxinas fonte.",
+ "apihelp-query+alltransclusions-param-prop": "Que partes de información incluír:\n;ids: Engade o ID da páxina da páxina transcluída (non pode usarse con $1unique).\n;title: Engade o título da transclusión.",
+ "apihelp-query+alltransclusions-param-namespace": "Nome de espazos a numerar.",
+ "apihelp-query+alltransclusions-param-limit": "Número total de obxectos a devolver.",
+ "apihelp-query+alltransclusions-param-dir": "Dirección na cal listar.",
+ "apihelp-query+alltransclusions-example-B": "Lista os títulos transcluídos, incluíndo os eliminados, cos ID das páxinas das que proveñen, comezando en <kbd>B</kbd>.",
+ "apihelp-query+alltransclusions-example-unique": "Lista os títulos transcluídos únicos.",
+ "apihelp-query+alltransclusions-example-unique-generator": "Obtén tódolos títulos transcluídos, marcando os eliminados.",
+ "apihelp-query+alltransclusions-example-generator": "Obtén as páxinas que conteñen as transclusións.",
+ "apihelp-query+allusers-description": "Enumerar tódolos usuarios rexistrados.",
+ "apihelp-query+allusers-param-from": "Nome de usuario para comezar a enumeración",
+ "apihelp-query+allusers-param-to": "Nome de usuario para rematar a enumeración.",
+ "apihelp-query+allusers-param-prefix": "Buscar tódolos nomes de usuario que comezan con este valor.",
+ "apihelp-query+allusers-param-dir": "Dirección na que ordenar.",
+ "apihelp-query+allusers-param-group": "Só incluír os usuarios nos grupos dados.",
+ "apihelp-query+allusers-param-excludegroup": "Excluír usuarios nos grupos dados.",
+ "apihelp-query+allusers-param-rights": "Incluír só ós usuarios cos dereitos dados. Non se inclúen grupo implícitos nin autopromocionados como *, usuario ou autoconfirmado.",
+ "apihelp-query+allusers-param-prop": "Que información incluír:\n;blockinfo:Engade información sobre o bloque actual do usuario.\n;groups:Lista de grupos nos que está o usuario. Isto usa máis recursos no servidor e pode devolver menos resultados que o límite.\n;implicitgroups:Lista todos os grupos ós que usuario pertence de forma automática.\n;rights:Lista os dereitos que ten o usuario.\n;editcount:Engade o número de edicións do usuario.\n;registration:Engade o selo de tempo do momento no que se rexistrou o usuario, se está dispoñible (pode ser branco).",
+ "apihelp-query+allusers-param-limit": "Número total de nomes de usuario a devolver.",
+ "apihelp-query+allusers-param-witheditsonly": "Só listar usuarios que teñan feito edicións.",
+ "apihelp-query+allusers-param-activeusers": "Só listar usuarios activos {{PLURAL:$1|no último día|nos $1 últimos días}}.",
+ "apihelp-query+allusers-example-Y": "Listar usuarios que comecen por <kbd>Y</kbd>.",
+ "apihelp-query+backlinks-description": "Atopar todas as páxinas que ligan coa páxina dada.",
+ "apihelp-query+backlinks-param-title": "Título a buscar. Non pode usarse xunto con <var>$1pageid</var>.",
+ "apihelp-query+backlinks-param-pageid": "Identificador de páxina a buscar. Non pode usarse xunto con <var>$1title</var>.",
+ "apihelp-query+backlinks-param-namespace": "Espazo de nomes a enumerar.",
+ "apihelp-query+backlinks-param-dir": "Dirección na cal listar.",
+ "apihelp-query+backlinks-param-filterredir": "Como filtrar as redireccións. Se o valor é <kbd>nonredirects</kbd> cando <var>$1redirect</var> está activa, só se aplica ó segundo nivel.",
+ "apihelp-query+backlinks-param-limit": "Cantas páxinas devolver. Se <var>$1redirect</var> está activa, aplícase o límite a cada nivel de forma separada (isto significa que poden devolverse ata 2 * <var>$1limit</var> resultados).",
+ "apihelp-query+backlinks-param-redirect": "Se a ligazón sobre unha páxina é unha redirección, atopa tamén todas as páxinas que ligan con esa redirección. O límite máximo divídese á metade.",
+ "apihelp-query+backlinks-example-simple": "Mostrar ligazóns á <kbd>Páxina principal<kbd>.",
+ "apihelp-query+backlinks-example-generator": "Obter a información das páxinas que ligan á <kbd>Páxina principal<kbd>.",
+ "apihelp-query+blocks-description": "Listar todos os usuarios e direccións IP bloqueados.",
+ "apihelp-query+blocks-param-start": "Selo de tempo para comezar a enumeración.",
+ "apihelp-query+blocks-param-end": "Selo de tempo para rematar a enumeración.",
+ "apihelp-query+blocks-param-ids": "Lista de IDs de bloque a listar (opcional).",
+ "apihelp-query+blocks-param-users": "Lista de usuarios a buscar (opcional).",
+ "apihelp-query+blocks-param-ip": "Obter todos os bloques aplicables a esta IPs ou a este rango CIDR, incluíndo bloques de rangos.\nNon pode usarse xunto con <var>$3users</var>. Os rangos CIDR maiores que IPv4/$1 ou IPv6/$2 non se aceptan.",
+ "apihelp-query+blocks-param-limit": "Número máximo de bloques a listar.",
+ "apihelp-query+blocks-param-show": "Só mostrar elementos correspondentes a eses criterios.\nPor exemplo, para ver só bloques indefinidos en direccións IP, ponga <kbd>$1show=ip|!temp</kbd>.",
+ "apihelp-query+blocks-example-simple": "Listar bloques.",
+ "apihelp-query+blocks-example-users": "Lista de bloques de usuarios <kbd>Alice</kbd> e <kbd>Bob</kbd>.",
+ "apihelp-query+categories-description": "Listar todas as categorías ás que pertencen as páxinas.",
+ "apihelp-query+categories-param-prop": "Que propiedades adicionais obter para cada categoría:\n;sortkey:Engade a clave de ordenación (cadea hexadecimal) e o prefixo da clave de ordenación (parte lexible) da categoría.\n;timestamp:Engade o selo de tempo de cando se engadíu a categoría.\n;hidden:Pon unha marca nas categorías que están ocultas con _&#95;HIDDENCAT_&#95;.",
+ "apihelp-query+categories-param-show": "Tipo de categorías a amosar.",
+ "apihelp-query+categories-param-limit": "Cantas categorías devolver.",
+ "apihelp-query+categories-param-categories": "Listar só esas categorías. Útil para verificar se unha páxina concreta está nunha categoría determinada.",
+ "apihelp-query+categories-param-dir": "Dirección na cal listar.",
+ "apihelp-query+categories-example-simple": "Obter a lista de categorías ás que pertence a páxina <kbd>Albert Einstein</kbd>",
+ "apihelp-query+categories-example-generator": "Obter a información de todas as categorías usadas na páxina <kbd>Albert Einstein</kbd>.",
+ "apihelp-query+categoryinfo-description": "Devolver información sobre as categorías dadas.",
+ "apihelp-query+categoryinfo-example-simple": "Obter información sobre <kbd>Category:Foo</kbd> e <kbd>Category:Bar</kbd>",
+ "apihelp-query+categorymembers-description": "Listar tódalas páxinas nunha categoría determinada.",
+ "apihelp-query+categorymembers-param-title": "Que categoría enumerar (obrigatorio). Debe incluír o prefixo <kbd>{{ns:category}}:</kbd>. Non pode usarse xunto con <var>$1pageid</var>.",
+ "apihelp-query+categorymembers-param-pageid": "ID de páxina da categoría a enumerar. Non se pode usar xunto con <var>$1title</var>.",
+ "apihelp-query+categorymembers-param-prop": "Que información incluír:\n;ids:Engade o ID da páxina.\n;title:Engade o título e o ID do espazo de nomes da páxina.\n;sortkey:Engade a clave de ordenación usada para ordenala na categoría (cadea hexadecimal).\n;sortkeyprefix:Engade o prefixo da clave de ordenación usado para ordenala na categoría (parte lexible da clave de ordenación).\n;type:Engade o tipo no que foi categorizado a páxina (páxina, subcategoría ou ficheiro)\n;timestamp:Engade o selo de tempo no que foi incluída a páxina.",
+ "apihelp-query+categorymembers-param-namespace": "Só incluír páxinas nestes espazos de nomes. Decátese de que poden usarse <kbd>$1type=subcat</kbd> ou <kbd>$1type=file</kbd> no canto de <kbd>$1namespace=14</kbd> ou <kbd>6</kbd>.",
+ "apihelp-query+categorymembers-param-type": "Que tipo de membros da categoría incluír. Ignorado cando está activo <kbd>$1sort=timestamp</kbd>.",
+ "apihelp-query+categorymembers-param-limit": "Máximo número de páxinas a retornar.",
+ "apihelp-query+categorymembers-param-sort": "Propiedade pola que ordenar.",
+ "apihelp-query+categorymembers-param-dir": "En que dirección ordenar.",
+ "apihelp-query+categorymembers-param-start": "Selo de tempo para comezar o listado. Só pode usarse con <kbd>$1sort=timestamp</kbd>.",
+ "apihelp-query+categorymembers-param-end": "Selo de tempo co que rematar o listado. Só pode usarse con <kbd>$1sort=timestamp</kbd>.",
+ "apihelp-query+categorymembers-param-starthexsortkey": "Chave de ordenación coa que comezar o listado, como se indique en <kbd>$1prop=sortkey</kbd>. Pode usarse só con <kbd>$1sort=sortkey</kbd>.",
+ "apihelp-query+categorymembers-param-endhexsortkey": "Chave de ordenación coa que rematar o listado, como se indique en <kbd>$1prop=sortkey</kbd>. Pode usarse só con <kbd>$1sort=sortkey</kbd>.",
+ "apihelp-query+categorymembers-param-startsortkeyprefix": "Prefixo da chave de ordenación coa que comezar o listado. Pode usarse só con <kbd>$1sort=sortkey</kbd>. Ignórase <var>$1starthexsortkey</var>.",
+ "apihelp-query+categorymembers-param-endsortkeyprefix": "Prefixo da chave de ordenación ANTES de rematar o listado (e non a, se existe este valor entón non será incluído!). Pode usarse só con <kbd>$1sort=sortkey</kbd>. Ignórase $1endhexsortkey.",
+ "apihelp-query+categorymembers-param-startsortkey": "Usar $1starthexsortkey no canto.",
+ "apihelp-query+categorymembers-param-endsortkey": "Usar $1endhexsortkey no canto.",
+ "apihelp-query+categorymembers-example-simple": "Obter as dez primeiras páxinas de <kbd>Category:Physics</kbd>.",
+ "apihelp-query+categorymembers-example-generator": "Obter a información das primeiras dez páxinas de <kbd>Category:Physics</kbd>.",
+ "apihelp-query+contributors-description": "Obter a lista de contribuidores conectados e o número de contribuidores anónimos dunha páxina.",
+ "apihelp-query+contributors-param-group": "Incluír só ós usuarios dos grupos dados. Non se inclúen grupos implícitos nin autopromocionados como *, usuario ou autoconfirmado.",
+ "apihelp-query+contributors-param-excludegroup": "Excluír usuarios nos grupos dados. Non se inclúen grupos implícitos nin autopromocionados como *, usuario ou autoconfirmado.",
+ "apihelp-query+contributors-param-rights": "Incluír só ós usuarios cos dereitos dados. Non se inclúen os dereitos dados a grupos implícitos nin autopromocionados como *, usuario ou autoconfirmado.",
+ "apihelp-query+contributors-param-excluderights": "Excluír usuarios cos dereitos dados. Non se inclúen os dereitos dados a grupos implícitos nin autopromocionados como *, usuario ou autoconfirmado.",
+ "apihelp-query+contributors-param-limit": "Número total de contribuidores a devolver.",
+ "apihelp-query+contributors-example-simple": "Mostrar os contribuidores á páxina <kbd>Main Page</kbd>.",
+ "apihelp-query+deletedrevisions-description": "Obter a información da revisión eliminada.\n\nPode usarse de varias formas:\n#Obter as revisións borradas dun conxunto de páxinas, indicando os títulos ou os IDs das páxinas. Ordenado por título e selo de tempo.\n#Obter datos sobre un conxunto de revisións borradas, indicando os seus IDs e os seus IDs de revisión. Ordenado por ID de revisión.",
+ "apihelp-query+deletedrevisions-param-start": "Selo de tempo no que comezar a enumeración. Ignorado cando se está procesando unha lista de IDs de revisións.",
+ "apihelp-query+deletedrevisions-param-end": "Selo de tempo no que rematar a enumeración. Ignorado cando se está procesando unha lista de IDs de revisións.",
+ "apihelp-query+deletedrevisions-param-tag": "Só listar revisións marcadas con esta etiqueta.",
+ "apihelp-query+deletedrevisions-param-user": "Só listar revisións deste usuario.",
+ "apihelp-query+deletedrevisions-param-excludeuser": "Non listar revisións deste usuario.",
+ "apihelp-query+deletedrevisions-param-limit": "Máximo número de revisións a listar.",
+ "apihelp-query+deletedrevisions-param-prop": "Que propiedades obter:\n;revid:Engade o ID da modificación borrada.\n;parentid:Engade o ID da modificación da modificación anterior da páxina.\n;user:Engade o usuario que fixo a modificación.\n;userid:Engade o ID do usuario que fixo a modificación.\n;comment:Engade o comentario da modificación.\n;parsedcomment:Engade o comentario analizado da modificación.\n;minor:Engade unha marca se a modificación é menor.\n;len:Engade a lonxitude (bytes) da modificación.\n;sha1:Engade a función SHA-1 (base 16) da modificación.\n;content:Engade o contido da modificación.\n;tags:Marcas da modificación.",
+ "apihelp-query+deletedrevisions-example-titles": "Listar as revisións borradas das páxinas <kbd>Main Page</kbd> e <kbd>Talk:Main Page</kbd>, con contido.",
+ "apihelp-query+deletedrevisions-example-revids": "Listar a información para a revisión borrada <kbd>123456</kbd>.",
+ "apihelp-query+deletedrevs-description": "Lista as modificación borradas.\n\nOpera según tres modos:\n#Lista as modificacións borradas dos títulos indicados, ordenados por selo de tempo.\n#Lista as contribucións borradas do usuario indicado, ordenadas por selo de tempo (sen indicar títulos).\n#Lista todas as modificacións borradas no espazo de nomes indicado, ordenadas por título e selo de tempo (sen indicar títulos, sen fixar $1user).\n\nCertos parámetros só se aplican a algúns modos e son ignorados noutros.",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Modo|Modos}}: $2",
+ "apihelp-query+deletedrevs-param-start": "Selo de tempo no que comezar a enumeración.",
+ "apihelp-query+deletedrevs-param-end": "Selo de tempo para rematar a enumeración.",
+ "apihelp-query+deletedrevs-param-from": "Comezar listado neste título.",
+ "apihelp-query+deletedrevs-param-to": "Rematar listado neste título.",
+ "apihelp-query+deletedrevs-param-prefix": "Buscar tódolos títulos de páxina que comezan con este valor.",
+ "apihelp-query+deletedrevs-param-unique": "Só listar unha revisión por cada páxina.",
+ "apihelp-query+deletedrevs-param-tag": "Só listar revisións marcadas con esta etiqueta.",
+ "apihelp-query+deletedrevs-param-user": "Só listar revisións deste usuario.",
+ "apihelp-query+deletedrevs-param-excludeuser": "Non listar revisións deste usuario.",
+ "apihelp-query+deletedrevs-param-namespace": "Só listar páxinas neste espazo de nomes.",
+ "apihelp-query+deletedrevs-param-limit": "Máximo número de revisións a listar.",
+ "apihelp-query+deletedrevs-example-mode1": "Listar as últimas revisións borradas das páxinas <kbd>Main Page</kbd> e <kbd>Talk:Main Page</kbd>, con contido (modo 1).",
+ "apihelp-query+deletedrevs-example-mode2": "Listar as últimas 50 contribucións borradas de <kbd>Bob</kbd> (modo 2).",
+ "apihelp-query+deletedrevs-example-mode3-main": "Listar as primeiras 50 revisións borradas no espazo de nomes principal (modo 3)",
+ "apihelp-query+deletedrevs-example-mode3-talk": "Listar as primeiras 50 páxinas no espazo de nomes {{ns:talk}} (modo 3).",
+ "apihelp-query+disabled-description": "Este módulo de consulta foi desactivado.",
+ "apihelp-query+duplicatefiles-description": "Listar todos os ficheiros que son duplicados dos fichieros dados baseado nos valores da función hash.",
+ "apihelp-query+duplicatefiles-param-limit": "Cantos ficheiros duplicados devolver.",
+ "apihelp-query+duplicatefiles-param-dir": "Dirección na cal listar.",
+ "apihelp-query+duplicatefiles-param-localonly": "Só buscar por ficheiros no repositorio local.",
+ "apihelp-query+duplicatefiles-example-simple": "Buscar duplicados de [[:File:Albert Einstein Head.jpg]]",
+ "apihelp-query+duplicatefiles-example-generated": "Buscar duplicados de tódolos ficheiros",
+ "apihelp-query+embeddedin-description": "Atopar todas as páxinas que inclúen (por transclusión) o título dado.",
+ "apihelp-query+embeddedin-param-title": "Título a buscar. Non pode usarse xunto con $1pageid.",
+ "apihelp-query+embeddedin-param-pageid": "Identificador de páxina a buscar. Non pode usarse xunto con $1title.",
+ "apihelp-query+embeddedin-param-namespace": "Espazo de nomes a enumerar.",
+ "apihelp-query+embeddedin-param-dir": "Dirección na cal listar.",
+ "apihelp-query+embeddedin-param-filterredir": "Como filtrar para redireccións.",
+ "apihelp-query+embeddedin-param-limit": "Número total de páxinas a devolver.",
+ "apihelp-query+embeddedin-example-simple": "Mostrar as páxinas que inclúan <kbd>Template:Stub</kbd>.",
+ "apihelp-query+embeddedin-example-generator": "Obter información sobre as páxinas que inclúen <kbd>Template:Stub</kbd>.",
+ "apihelp-query+extlinks-description": "Devolve todas as URLs externas (sen ser interwikis) das páxinas dadas.",
+ "apihelp-query+extlinks-param-limit": "Cantas ligazóns devolver.",
+ "apihelp-query+extlinks-param-protocol": "Protocolo da URL. Se está baleiro e está activo <var>$1query</var>, o protocolo é <kbd>http</kbd>. Deixar esa variable e a <var>$1query</var> baleiras para listar todas as ligazóns externas.",
+ "apihelp-query+extlinks-param-query": "Buscar cadea sen protocolo. Útil para verificar se unha páxina determinada contén unha URL externa determinada.",
+ "apihelp-query+extlinks-param-expandurl": "Expandir as URLs relativas a un protocolo co protocolo canónico.",
+ "apihelp-query+extlinks-example-simple": "Obter unha de ligazóns externas á <kbd>Páxina Principal<kbd>.",
+ "apihelp-query+exturlusage-description": "Enumerar páxinas que conteñen unha dirección URL dada.",
+ "apihelp-query+exturlusage-param-prop": "Que información incluír:\n;ids:Engade o ID da páxina.\n;title:Engade o título e o ID do espazo de nomes da páxina.\n;url:Engade a URL usada na páxina.",
+ "apihelp-query+exturlusage-param-protocol": "Protocolo da URL. Se está baleiro e está activo <var>$1query</var>, o protocolo é <kbd>http</kbd>. Deixar esa variable e a <var>$1query</var> baleiras para listar todas as ligazóns externas.",
+ "apihelp-query+exturlusage-param-query": "Buscar unha cadea sen protocolo. Ver [[Special:LinkSearch]]. Deixar baleira para listar todas as ligazóns externas.",
+ "apihelp-query+exturlusage-param-namespace": "Espazo de nomes a enumerar.",
+ "apihelp-query+exturlusage-param-limit": "Cantas páxinas devolver.",
+ "apihelp-query+exturlusage-param-expandurl": "Expandir as URLs relativas a un protocolo co protocolo canónico.",
+ "apihelp-query+exturlusage-example-simple": "Mostrar páxinas ligando a <kbd>http://www.mediawiki.org</kbd>.",
+ "apihelp-query+filearchive-description": "Enumerar secuencialmente todos os ficheiros borrados.",
+ "apihelp-query+filearchive-param-from": "Título da imaxe coa que comezar a enumeración.",
+ "apihelp-query+filearchive-param-to": "Título da imaxe coa que rematar a enumeración.",
+ "apihelp-query+filearchive-param-prefix": "Buscar tódolos títulos de imaxes que comezan con este valor.",
+ "apihelp-query+filearchive-param-limit": "Cantas imaxes devolver en total.",
+ "apihelp-query+filearchive-param-dir": "Dirección na cal listar.",
+ "apihelp-query+filearchive-param-sha1": "Función hash SHA1 da imaxe. Invalida $1sha1base36.",
+ "apihelp-query+filearchive-param-sha1base36": "Función hash SHA1 da imaxe en base 36 (usado en MediaWiki).",
+ "apihelp-query+filearchive-example-simple": "Mostrar unha lista de tódolos fichieiros eliminados.",
+ "apihelp-query+filerepoinfo-description": "Devolver a meta información sobre os repositorios de imaxes configurados na wiki.",
+ "apihelp-query+filerepoinfo-param-prop": "Que propiedades do repositorio mostrar (pode haber máis dispoñible nalgunhas wikis):\n;apiurl:URL ó API do repositorio - útil para obter información das imaxes no host.\n;name:A clave do repositorio - usada p. ex. nas variables de retorno de <var>[[mw:Manual:$wgForeignFileRepos|$wgForeignFileRepos]]</var> e [[Special:ApiHelp/query+imageinfo|imageinfo]]\n;displayname:O nome lexible do wiki repositorio.\n;rooturl:URL raíz dos camiños de imaxe.\n;local:Se o repositorio é o repositorio local ou non.",
+ "apihelp-query+filerepoinfo-example-simple": "Obter infomación sobre os repositorios de ficheiros",
+ "apihelp-query+fileusage-description": "Atopar tódalas páxinas que usan os ficheiros dados.",
+ "apihelp-query+fileusage-param-prop": "Que propiedades obter:\n;pageid:ID de cada páxina.\n;título:Título de cada páxina.\n;redirect:Marca de se a páxina é unha redirección.",
+ "apihelp-query+fileusage-param-namespace": "Só incluír páxinas nestes espazos de nomes.",
+ "apihelp-query+fileusage-param-limit": "Cantos mostrar.",
+ "apihelp-query+fileusage-param-show": "Mostrar só elementos que cumpren estes criterios:\n;redirect:Só mostra redireccións.\n;!redirect:Só mostra as que non son redireccións.",
+ "apihelp-query+fileusage-example-simple": "Obter unha lista de páxinas usando [[:File:Example.jpg]]",
+ "apihelp-query+fileusage-example-generator": "Obter infomación sobre páxinas que usan [[:File:Example.jpg]]",
+ "apihelp-query+imageinfo-description": "Devolve información de ficheiros e historial de subidas.",
+ "apihelp-query+imageinfo-param-prop": "Que información do ficheiro obter:",
+ "apihelp-query+imageinfo-paramvalue-prop-timestamp": "Engade selo de tempo á versión subida.",
+ "apihelp-query+imageinfo-paramvalue-prop-user": "Engade o usuario que subiu cada versión do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-userid": "Engade o ID de usuario que subiu cada versión do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-comment": "Comentario da versión.",
+ "apihelp-query+imageinfo-paramvalue-prop-parsedcomment": "Analizar o comentario da versión.",
+ "apihelp-query+imageinfo-paramvalue-prop-canonicaltitle": "Engade o título canónico do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-url": "Devolve a URL ó ficheiro e á páxina de descrición.",
+ "apihelp-query+imageinfo-paramvalue-prop-size": "Engade o tamaño do ficheiro en bytes e a altura, a anchura e o contador de páxina (se é aplicable).",
+ "apihelp-query+imageinfo-paramvalue-prop-dimensions": "Alias para o tamaño.",
+ "apihelp-query+imageinfo-paramvalue-prop-sha1": "Engade a función hash SHA-1 do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-mime": "Engade o tipo MIME do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-thumbmime": "Engade o tipo MIME da miniatura da imaxe (precisa a url e o parámetro $1urlwidth).",
+ "apihelp-query+imageinfo-paramvalue-prop-mediatype": "Engade o tipo do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-metadata": "Lista os metadatos Exif da versión do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-commonmetadata": "Lista os metadatos xenéricos do formato do ficheiro para a versión do ficheiro.",
+ "apihelp-query+imageinfo-paramvalue-prop-extmetadata": "Lista os metadatos combinados formateados de múltiples fontes. Os resultados están en formato HTML.",
+ "apihelp-query+imageinfo-paramvalue-prop-archivename": "Engade o nome de ficheiro da versión do ficheiro para versións anteriores ás últimas.",
+ "apihelp-query+imageinfo-paramvalue-prop-bitdepth": "Engade a profundidade de bits da versión.",
+ "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "Usado pola páxina Special:Upload para obter información sobre un ficheiro existente. Non previsto para usar fóra do núcleo MediaWiki.",
+ "apihelp-query+imageinfo-param-limit": "Cantas revisións de ficheiro a devolver por ficheiro.",
+ "apihelp-query+imageinfo-param-start": "Selo de tempo dende o que comezar a lista.",
+ "apihelp-query+imageinfo-param-end": "Selo de tempo no que rematar a lista.",
+ "apihelp-query+imageinfo-param-urlwidth": "Se $2prop=url está definido, será devolta unha URL a unha imaxe escalada a este ancho.\nPor razóns de rendimento se se usa esta opción, non se devolverán máis de $1 imaxes.",
+ "apihelp-query+imageinfo-param-urlheight": "Similar a $1urlwidth.",
+ "apihelp-query+imageinfo-param-metadataversion": "Versión de metadata a usar. Se <kbd>latest</kbd> está especificado, usa a última versión. Por defecto <kbd>1</kbd> para compatibilidade con versións anteriores.",
+ "apihelp-query+imageinfo-param-extmetadatalanguage": "Que lingua buscar en extmetadata. Isto afecta tanto á tradución a buscar, se hai varias dispoñibles, como a como se formatean cousas como os números e outros valores.",
+ "apihelp-query+imageinfo-param-extmetadatamultilang": "Se as traducións para a propiedade extmetadata están dispoñibles, búscaas todas.",
+ "apihelp-query+imageinfo-param-extmetadatafilter": "Se está especificado e non baleiro, só se devolverán esas claves para $1prop=extmetadata.",
+ "apihelp-query+imageinfo-param-localonly": "Só buscar ficheiros no repositorio local.",
+ "apihelp-query+imageinfo-example-simple": "Busca a información sobre a versión actual de [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+imageinfo-example-dated": "Busca información sobre as versións de [[:File:Test.jpg]] posteriores a 2008.",
+ "apihelp-query+images-description": "Devolve todos os ficheiros contidos nas páxinas dadas.",
+ "apihelp-query+images-param-limit": "Cantos ficheiros devolver.",
+ "apihelp-query+images-param-images": "Listar só eses ficheiros. Útil para verificar se unha páxina concreta ten un ficheiro determinado.",
+ "apihelp-query+images-param-dir": "Dirección na cal listar.",
+ "apihelp-query+images-example-simple": "Obter unha lista de arquivos empregados na [[Main Page]].",
+ "apihelp-query+images-example-generator": "Obter información sobre todos os ficheiros usados na [[Main Page]].",
+ "apihelp-query+imageusage-description": "Atopar tódalas páxinas que usan o título da imaxe dada.",
+ "apihelp-query+imageusage-param-title": "Título a buscar. Non pode usarse xunto con $1pageid.",
+ "apihelp-query+imageusage-param-pageid": "ID de páxina a buscar. Non pode usarse xunto con $1title.",
+ "apihelp-query+imageusage-param-namespace": "Nome de espazos a numerar.",
+ "apihelp-query+imageusage-param-dir": "Dirección na cal listar.",
+ "apihelp-query+imageusage-param-filterredir": "Como filtrar redireccións. Se se fixa a non redirección cando está activo $1redirect, isto só se aplica ó segundo nivel.",
+ "apihelp-query+imageusage-param-limit": "Cantas páxinas devolver. Se <var>$1redirect</var> está activa, aplícase o límite a cada nivel de forma separada (isto significa que poden devolverse ata 2 * <var>$1limit</var> resultados).",
+ "apihelp-query+imageusage-param-redirect": "Se a ligazón sobre unha páxina é unha redirección, atopa tamén todas as páxinas que ligan con esa redirección. O límite máximo divídese á metade.",
+ "apihelp-query+imageusage-example-simple": "Mostrar as páxinas que usan [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+imageusage-example-generator": "Obter información sobre as páxinas que usan [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+info-description": "Obter información básica da páxina.",
+ "apihelp-query+info-param-prop": "Que propiedades adicionais obter:",
+ "apihelp-query+info-paramvalue-prop-protection": "Listar o nivel de protección de cada páxina.",
+ "apihelp-query+info-paramvalue-prop-talkid": "O ID de páxina da páxina de conversa para cada páxina que non é páxina de conversa.",
+ "apihelp-query+info-paramvalue-prop-watched": "Listar o estado de vixiancia de cada páxina.",
+ "apihelp-query+info-paramvalue-prop-watchers": "O número de vixiantes, se está permitido.",
+ "apihelp-query+info-paramvalue-prop-notificationtimestamp": "O selo de tempo de notificación da lista de vixiancia de cada páxina.",
+ "apihelp-query+info-paramvalue-prop-subjectid": "O ID de páxina da páxina pai para cada páxina de conversa.",
+ "apihelp-query+info-paramvalue-prop-url": "Devolve unha URL completa, unha URL de modificación, e a URL canónica de cada páxina.",
+ "apihelp-query+info-paramvalue-prop-readable": "Se o usuario pode ler esta páxina.",
+ "apihelp-query+info-paramvalue-prop-preload": "Devolve o texto devolto por EditFormPreloadText.",
+ "apihelp-query+info-paramvalue-prop-displaytitle": "Devolve a forma na que se visualiza actualmente o título da páxina.",
+ "apihelp-query+info-param-testactions": "Proba se o usuario actual pode realizar certas accións na páxina.",
+ "apihelp-query+info-param-token": "Usar [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] no canto diso.",
+ "apihelp-query+info-example-simple": "Obter información sobre a páxina <kbd>Main Page</kbd>.",
+ "apihelp-query+info-example-protection": "Obter información xeral e de protección sobre a páxina <kbd>Main Page</kbd>.",
+ "apihelp-query+iwbacklinks-description": "Atopar todas as páxina que ligan á ligazón interwiki indicada.\n\nPode usarse para atopar todas as ligazóns cun prefixo, ou todas as ligazóns a un título (co prefixo indicado). Se non se usa ningún parámetro funciona como \"todas as ligazóns interwiki\".",
+ "apihelp-query+iwbacklinks-param-prefix": "Prefixo para a interwiki.",
+ "apihelp-query+iwbacklinks-param-title": "Ligazón interwiki a buscar. Debe usarse con <var>$1blprefix</var>.",
+ "apihelp-query+iwbacklinks-param-limit": "Número total de páxinas a devolver.",
+ "apihelp-query+iwbacklinks-param-prop": "Que propiedades obter:\n;iwprefix:Engade o prefixo da interwiki.\n;iwtitle:Engade o título da interwiki.",
+ "apihelp-query+iwbacklinks-param-dir": "Dirección na cal listar.",
+ "apihelp-query+iwbacklinks-example-simple": "Obter as páxinas ligadas a [[wikibooks:Test]]",
+ "apihelp-query+iwbacklinks-example-generator": "Obter información sobre as páxinas que ligan a [[wikibooks:Test]].",
+ "apihelp-query+iwlinks-description": "Devolve todas as ligazóns interwiki ás páxinas indicadas.",
+ "apihelp-query+iwlinks-param-url": "Se obter a URL completa (non pode usarse con $1prop).",
+ "apihelp-query+iwlinks-param-prop": "Que propiedades adicionais obter para cada ligazón interwiki:\n;url:Engade a URL completa.",
+ "apihelp-query+iwlinks-param-limit": "Cantas ligazóns interwiki devolver.",
+ "apihelp-query+iwlinks-param-prefix": "Só devolver ligazóns interwiki con este prefixo.",
+ "apihelp-query+iwlinks-param-title": "Ligazón interwiki a buscar. Debe usarse con <var>$1prefix</var>.",
+ "apihelp-query+iwlinks-param-dir": "Dirección na cal listar.",
+ "apihelp-query+iwlinks-example-simple": "Obter as ligazóns interwiki da páxina <kbd>Main Page</kbd>.",
+ "apihelp-query+langbacklinks-description": "Atopar todas as páxinas que ligan coa ligazón de lingua dada. \n\nPode usarse para atopar todas as ligazóns cun código de lingua, ou todas as ligazón a un título (cunha lingua dada). Non usar cun parámetro que sexa \"todas as ligazóns de lingua\".\n\nDecátese que isto pode non considerar as ligazóns de idioma engadidas polas extensións.",
+ "apihelp-query+langbacklinks-param-lang": "Lingua para a ligazón de lingua.",
+ "apihelp-query+langbacklinks-param-title": "Ligazón de lingua a buscar. Debe usarse con $1lang.",
+ "apihelp-query+langbacklinks-param-limit": "Número total de páxinas a devolver.",
+ "apihelp-query+langbacklinks-param-prop": "Que propiedades obter:\n;lllang:Engade o código de lingua á ligazón de páxina.\n;lltitle:Engade o título da ligazón de lingua.",
+ "apihelp-query+langbacklinks-param-dir": "Dirección na cal listar.",
+ "apihelp-query+langbacklinks-example-simple": "Obter as páxinas ligadas a [[:fr:Test]].",
+ "apihelp-query+langbacklinks-example-generator": "Obter información sobre as páxinas que ligan a [[:fr:Test]].",
+ "apihelp-query+langlinks-description": "Devolve todas as ligazóns interwiki ás páxinas indicadas.",
+ "apihelp-query+langlinks-param-limit": "Cantas ligazóns de lingua devolver.",
+ "apihelp-query+langlinks-param-url": "Se obter a URL completa (non pode usarse con <var>$1prop</var>).",
+ "apihelp-query+langlinks-param-prop": "Que propiedades adicionais obter para cada ligazón interlingüística:\n;url:Engade a URL completa.\n;langname:Engade o nome localizado da lingua (o mellor intento). Use <var>$1inlanguagecode</var> para controlar a lingua.\n;autonym:Engade o nome nativo da lingua.",
+ "apihelp-query+langlinks-param-lang": "Devolver só ligazóns de lingua con este código de lingua.",
+ "apihelp-query+langlinks-param-title": "Ligazón a buscar. Debe usarse con <var>$1lang</var>.",
+ "apihelp-query+langlinks-param-dir": "Dirección na cal listar.",
+ "apihelp-query+langlinks-param-inlanguagecode": "Código de lingua para nomes de lingua localizados.",
+ "apihelp-query+langlinks-example-simple": "Obter ligazóns interlingua da páxina <kbd>Main Page</kbd>.",
+ "apihelp-query+links-description": "Devolve todas as ligazóns das páxinas indicadas.",
+ "apihelp-query+links-param-namespace": "Mostra ligazóns só neste espazo de nomes.",
+ "apihelp-query+links-param-limit": "Cantas ligazóns devolver.",
+ "apihelp-query+links-param-titles": "Listar só as ligazóns a eses títulos. Útil para verificar se unha páxina concreta liga a un título determinado.",
+ "apihelp-query+links-param-dir": "Dirección na cal listar.",
+ "apihelp-query+links-example-simple": "Obter as ligazóns da páxina <kbd>Main Page</kbd>.",
+ "apihelp-query+links-example-generator": "Obter información sobre as ligazóns de páxina da <kbd>Main Page</kbd>.",
+ "apihelp-query+links-example-namespaces": "Obter as ligazóns á páxina <kbd>Main Page</kbd> nos espazos de nome {{ns:user}} e {{ns:template}}.",
+ "apihelp-query+linkshere-description": "Atopar todas as páxinas que ligan coas páxinas dadas.",
+ "apihelp-query+linkshere-param-prop": "Que propiedades obter:\n;pageid:ID de cada páxina.\n;título:Título de cada páxina.\n;redirect:Marca de se a páxina é unha redirección.",
+ "apihelp-query+linkshere-param-namespace": "Só incluír páxinas nestes espazos de nomes.",
+ "apihelp-query+linkshere-param-limit": "Cantos mostrar.",
+ "apihelp-query+linkshere-param-show": "Mostrar só elementos que cumpren estes criterios:\n;redirect:Só mostra redireccións.\n;!redirect:Só mostra as que non son redireccións.",
+ "apihelp-query+linkshere-example-simple": "Obter unha lista que ligan á [[Main Page]]",
+ "apihelp-query+linkshere-example-generator": "Obter a información das páxinas que ligan á [[Main Page]].",
+ "apihelp-query+logevents-description": "Obter os eventos dos rexistros.",
+ "apihelp-query+logevents-param-type": "Filtrar as entradas do rexistro para mostrar só as deste tipo.",
+ "apihelp-query+logevents-param-action": "Filtrar accións no rexistro para mostrar só esta acción. Ignora <var>$1type</var>. Accións comodín como <kbd>action/*</kbd> permiten especificar calquera cadea para o asterisco.",
+ "apihelp-query+logevents-param-start": "Selo de tempo no que comezar a enumeración.",
+ "apihelp-query+logevents-param-end": "Selo de tempo para rematar a enumeración.",
+ "apihelp-query+logevents-param-user": "Filtrar entradas ás feitas polo usuario indicado.",
+ "apihelp-query+logevents-param-title": "Filtrar entradas ás asociadas á páxina indicada.",
+ "apihelp-query+logevents-param-namespace": "Filtrar entradas ás do espazo de nomes indicado.",
+ "apihelp-query+logevents-param-prefix": "Filtrar entradas ás que comezan por este prefixo.",
+ "apihelp-query+logevents-param-tag": "Só listar entradas de evento marcadas con esta etiqueta.",
+ "apihelp-query+logevents-param-limit": "Número total de entradas de evento a devolver.",
+ "apihelp-query+logevents-example-simple": "Lista de eventos recentes do rexistro.",
+ "apihelp-query+pagepropnames-description": "Listar os nomes de todas as propiedades de páxina usados na wiki.",
+ "apihelp-query+pagepropnames-param-limit": "Máximo número de nomes a retornar.",
+ "apihelp-query+pagepropnames-example-simple": "Obter os dez primeiros nomes de propiedade.",
+ "apihelp-query+pageprops-description": "Obter varias propiedades definidas no contido da páxina.",
+ "apihelp-query+pageprops-param-prop": "Listar só esas propiedades. Útil para verificar se unha páxina concreta usa unha propiedade de páxina determinada.",
+ "apihelp-query+pageprops-example-simple": "Obter as propiedades para <kbd>Category:Foo</kbd>.",
+ "apihelp-query+pageswithprop-description": "Mostrar a lista de páxinas que empregan unha propiedade determinada.",
+ "apihelp-query+pageswithprop-param-propname": "Propiedade de páxina pola que enumerar as páxinas.",
+ "apihelp-query+pageswithprop-param-prop": "Que información incluír:\n;ids:Engade o ID da páxina.\n;title:Engade o título e o ID do espazo de nomes da páxina.\n;value:Engade o valor da propiedade da páxina.",
+ "apihelp-query+pageswithprop-param-limit": "Máximo número de páxinas a retornar.",
+ "apihelp-query+pageswithprop-param-dir": "En que dirección ordenar.",
+ "apihelp-query+pageswithprop-example-simple": "Lista as dez primeiras páxinas que usan <code>&#123;&#123;DISPLAYTITLE:&#125;&#125;</code>.",
+ "apihelp-query+pageswithprop-example-generator": "Obter a infomación de páxina das dez primeiras páxinas que usan <code>_&#95;NOTOC_&#95;</code>.",
+ "apihelp-query+prefixsearch-description": "Facer unha busca de prefixo nos títulos das páxinas.",
+ "apihelp-query+prefixsearch-param-search": "Buscar texto.",
+ "apihelp-query+prefixsearch-param-namespace": "Espazo de nomes no que buscar.",
+ "apihelp-query+prefixsearch-param-limit": "Número máximo de resultados a visualizar.",
+ "apihelp-query+prefixsearch-param-offset": "Número de resultados a saltar.",
+ "apihelp-query+prefixsearch-example-simple": "Buscar títulos de páxina que comecen con <kbd>meaning</kbd>.",
+ "apihelp-query+protectedtitles-description": "Listar todos os títulos protexidos en creación.",
+ "apihelp-query+protectedtitles-param-namespace": "Só listar títulos nestes espazos de nomes.",
+ "apihelp-query+protectedtitles-param-level": "Só listar títulos con estos niveis de protección.",
+ "apihelp-query+protectedtitles-param-limit": "Número total de páxinas a devolver.",
+ "apihelp-query+protectedtitles-param-start": "Comezar a listar neste selo de tempo de protección.",
+ "apihelp-query+protectedtitles-param-end": "Rematar de listar neste selo de tempo de protección.",
+ "apihelp-query+protectedtitles-param-prop": "Que propiedades obter:\n;timestamp:Engade o selo de tempo de cando se fixo a protección.\n;user:Engade o usuario que fixo a protección.\n;userid:Engade o ID do usuario que fixo a protección.\n;comment:Engade o comentario da protección.\n;parsedcomment:Engade o comentario analizado da protección.\n;expiry:Engade o selo de tempo no que rematará a protección\n;level:Engade o nivel de protección.",
+ "apihelp-query+protectedtitles-example-simple": "Listar títulos protexidos",
+ "apihelp-query+protectedtitles-example-generator": "Atopar ligazóns ós títulos protexidos no espazo de nomes principal",
+ "apihelp-query+querypage-description": "Obtén unha lista proporcionada por unha páxina especial basada en QueryPage.",
+ "apihelp-query+querypage-param-page": "Nome da páxina especial. Teña en conta que diferencia entre maiúsculas e minúsculas.",
+ "apihelp-query+querypage-param-limit": "Número de resultados a visualizar.",
+ "apihelp-query+querypage-example-ancientpages": "Resultados devoltos de [[Special:Ancientpages]].",
+ "apihelp-query+random-param-namespace": "Devolver páxinas só neste espazo de nomes.",
+ "apihelp-query+random-param-limit": "Limitar cantas páxinas aleatorias se van devolver.",
+ "apihelp-query+random-param-redirect": "Cargar unha redirección aleatoria no canto dunha páxina aleatoria.",
+ "apihelp-query+random-example-simple": "Obter dúas páxinas aleatorias do espazo de nomes principal.",
+ "apihelp-query+random-example-generator": "Obter a información da páxina de dúas páxinas aleatorias do espazo de nomes principal.",
+ "apihelp-query+recentchanges-description": "Enumerar cambios recentes.",
+ "apihelp-query+recentchanges-param-start": "Selo de tempo para comezar a enumeración.",
+ "apihelp-query+recentchanges-param-end": "Selo de tempo para rematar a enumeración.",
+ "apihelp-query+recentchanges-param-namespace": "Filtrar os cambios a só eses espazos de nomes.",
+ "apihelp-query+recentchanges-param-user": "Só listar cambios deste usuario.",
+ "apihelp-query+recentchanges-param-excludeuser": "Non listar cambios deste usuario.",
+ "apihelp-query+recentchanges-param-tag": "Só listar cambios marcados con esta etiqueta.",
+ "apihelp-query+recentchanges-param-token": "Usar <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> no canto diso.",
+ "apihelp-query+recentchanges-param-show": "Só mostrar elementos que cumpran esos criterios. Por exemplo, para ver só edicións menores feitas por usuarios conectados, activar $1show=minor|!anon.",
+ "apihelp-query+recentchanges-param-limit": "Número total de páxinas a devolver.",
+ "apihelp-query+recentchanges-param-type": "Que tipos de cambios mostrar.",
+ "apihelp-query+recentchanges-param-toponly": "Listar só cambios que son a última revisión.",
+ "apihelp-query+recentchanges-example-simple": "Listar cambios recentes.",
+ "apihelp-query+recentchanges-example-generator": "Obter a información de páxina sobre cambios recentes sen vixiancia.",
+ "apihelp-query+redirects-description": "Devolve todas as redireccións das páxinas indicadas.",
+ "apihelp-query+redirects-param-prop": "Que propiedades recuperar:\n;pageid:ID de páxina de cada redirección.\n;title:Título de cada redirección.\n;fragment:Fragmento de cada redirección, se hai algún.",
+ "apihelp-query+redirects-param-namespace": "Só incluir páxinas nestes espacios de nomes.",
+ "apihelp-query+redirects-param-limit": "Cantos redireccións devolver.",
+ "apihelp-query+redirects-param-show": "Só mostrar elementos que cumpran estos criterios:\n;fragment:Só mostrar redireccións que teñan un fragmento.\n;!fragment:Só mostrar redireccións que non teñan un fragmento.",
+ "apihelp-query+redirects-example-simple": "Obter unha lista de redireccións á [[Main Page]]",
+ "apihelp-query+redirects-example-generator": "Obter información sobre tódalas redireccións á [[Main Page]]",
+ "apihelp-query+revisions-description": "Obter información da modificación.\n\nPode usarse de varias formas:\n#Obter datos sobre un conxunto de páxinas (última modificación), fixando os títulos ou os IDs das páxinas.\n#Obter as modificacións da páxina indicada, usando os títulos ou os IDs de páxinas con comezar, rematar ou límite.\n#Obter os datos sobre un conxunto de modificacións fixando os seus IDs cos seus IDs de modificación.",
+ "apihelp-query+revisions-paraminfo-singlepageonly": "Só pode usarse cunha única páxina (mode #2).",
+ "apihelp-query+revisions-param-startid": "Desde que ID de revisión comezar a enumeración.",
+ "apihelp-query+revisions-param-endid": "Rematar a enumeración de revisión neste ID de revisión.",
+ "apihelp-query+revisions-param-start": "Desde que selo de tempo comezar a enumeración.",
+ "apihelp-query+revisions-param-end": "Enumerar desde este selo de tempo.",
+ "apihelp-query+revisions-param-user": "Só incluir revisión feitas polo usuario.",
+ "apihelp-query+revisions-param-excludeuser": "Excluír revisións feitas polo usuario.",
+ "apihelp-query+revisions-param-tag": "Só listar revisións marcadas con esta etiqueta.",
+ "apihelp-query+revisions-param-token": "Que identificadores obter para cada revisión.",
+ "apihelp-query+revisions-example-content": "Obter datos con contido da última revisión dos títulos <kbd>API</kbd> e <kbd>Main Page</kbd>.",
+ "apihelp-query+revisions-example-last5": "Mostrar as cinco últimas revisión da <kbd>Páxina Principal</kbd>.",
+ "apihelp-query+revisions-example-first5": "Mostar as cinco primeiras revisións da <kbd>Páxina Principal</kbd>.",
+ "apihelp-query+revisions-example-first5-after": "Mostrar as cinco primeiras revisións da <kbd>Páxina Principal</kbd> feitas despois de 2006-05-01.",
+ "apihelp-query+revisions-example-first5-not-localhost": "Mostrar as cinco primeiras revisións da <kbd>Páxina Principal</kbd> que non foron feitas polo usuario anónimo <kbd>127.0.0.1</kbd>.",
+ "apihelp-query+revisions-example-first5-user": "Mostrar as cinco primeiras revisión da <kbd>Páxina Principal</kbd> feitas polo usuario <kbd>MediaWiki default</kbd>.",
+ "apihelp-query+revisions+base-param-limit": "Limitar cantas revisións se van devolver.",
+ "apihelp-query+revisions+base-param-expandtemplates": "Expandir os modelos no contido da revisión (require $1prop=content).",
+ "apihelp-query+revisions+base-param-generatexml": "Xenerar a árbore de análise XML para o contido da revisión (require $1prop=content).",
+ "apihelp-query+revisions+base-param-parse": "Analizar o contido da revisión (require $1prop=content). Por razóns de rendemento, se se usa esta opción, $1limit cámbiase a 1.",
+ "apihelp-query+revisions+base-param-section": "Recuperar unicamente o contido deste número de sección.",
+ "apihelp-query+revisions+base-param-diffto": "ID de revisión a comparar con cada revisión. Use <kbd>prev</kbd>, <kbd>next</kbd> e <kbd>cur</kbd> para a versión precedente, seguinte e actual respectivamente.",
+ "apihelp-query+revisions+base-param-difftotext": "Texto co que comparar cada revisión. Só compara un número limitado de revisións. Ignora <var>$1diffto</var>. Se <var>$1section</var> ten valor, só se comparará co texto esa sección.",
+ "apihelp-query+revisions+base-param-contentformat": "Formato de serialización usado por <var>$1difftotext</var> e esperado para a saída do contido.",
+ "apihelp-query+search-description": "Facer unha busca por texto completo.",
+ "apihelp-query+search-param-search": "Buscar tódolos títulos de páxina (ou contido) que teñan este valor.",
+ "apihelp-query+search-param-namespace": "Buscar só nestes espazos de nomes.",
+ "apihelp-query+search-param-what": "Que tipo de busca lanzar.",
+ "apihelp-query+search-param-info": "Que metadatos devolver.",
+ "apihelp-query+search-param-limit": "Número total de páxinas a devolver.",
+ "apihelp-query+search-param-interwiki": "Incluir na busca resultados de interwikis, se é posible.",
+ "apihelp-query+search-param-backend": "Que servidor de busca usar, se non se indica usa o que hai por defecto.",
+ "apihelp-query+search-example-simple": "Buscar por <kbd>significado</kbd>.",
+ "apihelp-query+search-example-text": "Buscar texto por <kbd>significado</kbd>.",
+ "apihelp-query+search-example-generator": "Obter información da páxina sobre as páxinas devoltas por unha busca por <kbd>significado</kbd>.",
+ "apihelp-query+siteinfo-description": "Devolver información xeral sobre o sitio.",
+ "apihelp-query+siteinfo-param-filteriw": "Só devolver entradas locais ou só non locais da correspondencia interwiki.",
+ "apihelp-query+siteinfo-param-showalldb": "Listar todos os servidores de base de datos, non só o que teña máis retardo.",
+ "apihelp-query+siteinfo-param-numberingroup": "Listar o número de usuarios nos grupos de usuarios.",
+ "apihelp-query+siteinfo-param-inlanguagecode": "Código de lingua para os nomes de lingua localizados (a mellor forma posible) e nomes de presentación.",
+ "apihelp-query+siteinfo-example-simple": "Obter información do sitio.",
+ "apihelp-query+siteinfo-example-interwiki": "Obter unha lista de prefixos interwiki locais.",
+ "apihelp-query+siteinfo-example-replag": "Revisar o retardo de replicación actual.",
+ "apihelp-query+stashimageinfo-description": "Devolve a información dos ficheiros almacenados.",
+ "apihelp-query+stashimageinfo-param-filekey": "Clave que identifica unha subida precedente e que foi almacenada temporalmente.",
+ "apihelp-query+stashimageinfo-param-sessionkey": "Alias para $1filekey, para compatibilidade con versións antigas.",
+ "apihelp-query+stashimageinfo-example-simple": "Devolve a información dun ficheiro almacenado.",
+ "apihelp-query+stashimageinfo-example-params": "Devolve as miniaturas de dous ficheiros almacenados.",
+ "apihelp-query+tags-description": "Lista de marcas de cambios.",
+ "apihelp-query+tags-param-limit": "Máximo número de etiquetas a listar.",
+ "apihelp-query+tags-example-simple": "Listar as marcas dispoñibles",
+ "apihelp-query+templates-description": "Devolve todas as páxinas incluídas na páxina indicada.",
+ "apihelp-query+templates-param-namespace": "Mostrar modelos só neste espazo de nomes.",
+ "apihelp-query+templates-param-limit": "Número de modelos a devolver.",
+ "apihelp-query+templates-param-templates": "Listar só eses modelos. Útil para verificar se unha páxina concreta ten un modelo determinado.",
+ "apihelp-query+templates-param-dir": "Dirección na cal listar.",
+ "apihelp-query+templates-example-simple": "Coller os modelos usado na <kbd>Páxina Principal</kbd>.",
+ "apihelp-query+templates-example-generator": "Obter información sobre os modelos usados na <kbd>Páxina Principal</kbd>.",
+ "apihelp-query+templates-example-namespaces": "Obter páxinas nos espazos de nomes {{ns:user}} e {{ns:template}} que se transclúen na <kbd>Páxina Principal</kbd>.",
+ "apihelp-query+tokens-description": "Recupera os identificadores das accións de modificación de datos.",
+ "apihelp-query+tokens-param-type": "Tipos de identificadores a consultar.",
+ "apihelp-query+tokens-example-simple": "Recuperar un identificador csrf (por defecto).",
+ "apihelp-query+tokens-example-types": "Recuperar un identificador vixiancia e un de patrulla.",
+ "apihelp-query+transcludedin-description": "Atopar todas as páxinas que inclúen ás páxinas indicadas.",
+ "apihelp-query+transcludedin-param-prop": "Que propiedades obter:\n;pageid:ID de páxina de cada páxina.\n;title:Título de cada páxina.\n;redirect:Marca si a páxina é unha redirección.",
+ "apihelp-query+transcludedin-param-namespace": "Só incluir páxinas nestes espacios de nomes.",
+ "apihelp-query+transcludedin-param-limit": "Cantos mostrar.",
+ "apihelp-query+transcludedin-param-show": "Mostrar só elementos que cumpren estes criterios:\n;redirect:Só mostra redireccións.\n;!redirect:Só mostra as que non son redireccións.",
+ "apihelp-query+transcludedin-example-simple": "Obter unha lista de páxinas que inclúen a <kbd>Main Page</kbd>.",
+ "apihelp-query+transcludedin-example-generator": "Obter información sobre as páxinas que inclúen <kbd>Main Page</kbd>.",
+ "apihelp-query+usercontribs-description": "Mostrar tódalas edicións dun usuario.",
+ "apihelp-query+usercontribs-param-limit": "Máximo número de contribucións a mostar.",
+ "apihelp-query+usercontribs-param-start": "Selo de tempo de comezo ó que volver.",
+ "apihelp-query+usercontribs-param-end": "Selo de tempo de fin ó que volver.",
+ "apihelp-query+usercontribs-param-user": "Usuarios para os que recuperar as contribucións.",
+ "apihelp-query+usercontribs-param-userprefix": "Recuperar as contribucións de todos os usuarios cuxo nome comece por este valor. Ignora $1user.",
+ "apihelp-query+usercontribs-param-namespace": "Só listar contribucións nestes espazos de nomes.",
+ "apihelp-query+usercontribs-param-show": "Só mostrar elementos que cumpran estos criterios, p.ex. só edicións menores: <kbd>$2show=!minor</kbd>.\n\nSe está fixado <kbd>$2show=patrolled</kbd> ou <kbd>$2show=!patrolled</kbd>, as modificacións máis antigas que <var>[[mw:Manual:$wgRCMaxAge|$wgRCMaxAge]]</var> ($1 {{PLURAL:$1|segundo|segundos}}) non se mostrarán.",
+ "apihelp-query+usercontribs-param-tag": "Só listar revisións marcadas con esta etiqueta.",
+ "apihelp-query+usercontribs-param-toponly": "Listar só cambios que son a última revisión.",
+ "apihelp-query+usercontribs-example-user": "Mostrar as contribucións do usuario <kbd>Exemplo</kbd>.",
+ "apihelp-query+usercontribs-example-ipprefix": "Mostrar contribucións de tódalas direccións IP que comezan por <kbd>192.0.2.</kbd>.",
+ "apihelp-query+userinfo-description": "Obter información sobre o usuario actual.",
+ "apihelp-query+userinfo-example-simple": "Obter información sobre o usuario actual.",
+ "apihelp-query+userinfo-example-data": "Obter información adicional sobre o usuario actual.",
+ "apihelp-query+users-description": "Obter información sobre unha lista de usuarios.",
+ "apihelp-query+users-param-prop": "Que información incluír:\n;blockinfo:Etiquetas se o usuario está bloqueado, por quen, e por que razón.\n;groups:Lista todos os grupos ós que pertence cada usuario.\n;implicitgroups:Lista os grupos dos que un usuario é membro de forma automatica.\n;rights:Lista todos os dereitos que ten cada usuario.\n;editcount:Engade o contador de edicións do usuario.\n;registration:Engade o selo de tempo do rexistro do usuario.\n;emailable:Marca se o usuario pode e quere recibir correos usando [[Special:Emailuser]].\n;gender:Marca o xénero do usuario. Devolve \"home\", \"muller\" ou \"descoñecido\".",
+ "apihelp-query+users-param-users": "Lista de usuarios para os que obter información.",
+ "apihelp-query+users-param-token": "Usar <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> no canto diso.",
+ "apihelp-query+users-example-simple": "Mostar información para o usuario <kbd>Exemplo</kbd>.",
+ "apihelp-query+watchlist-description": "Ver os cambios recentes das páxinas na lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlist-param-allrev": "Incluír múltiples revisións da mesma páxina dentro do intervalo de tempo indicado.",
+ "apihelp-query+watchlist-param-start": "Selo de tempo para comezar a enumeración",
+ "apihelp-query+watchlist-param-end": "Selo de tempo para rematar a enumeración.",
+ "apihelp-query+watchlist-param-namespace": "Filtrar os cambios a só os espazos de nomes indicados.",
+ "apihelp-query+watchlist-param-user": "Só listar cambios deste usuario.",
+ "apihelp-query+watchlist-param-excludeuser": "Non listar cambios deste usuario.",
+ "apihelp-query+watchlist-param-limit": "Cantos resultados totais mostrar por petición.",
+ "apihelp-query+watchlist-param-show": "Só mostrar elementos que cumpran esos criterios. Por exemplo, para ver só edicións menores feitas por usuarios conectados, activar $1show=minor|!anon.",
+ "apihelp-query+watchlist-param-type": "Que tipos de cambios mostrar:\n;edit:Modificacións normais de páxina.\n;external:Modificacións externas.\n;new:Creación de páxinas.\n;log:Entradas no rexistro.",
+ "apihelp-query+watchlist-param-owner": "Usado con $1token para acceder á lista de páxinas de vixiancia doutro usuario.",
+ "apihelp-query+watchlist-param-token": "Identificador de seguridade (dispoñible nas [[Special:Preferences#mw-prefsection-watchlist|preferencias]] de usuario) para permitir o acceso a outros á súa páxina de vixiancia.",
+ "apihelp-query+watchlist-example-simple": "Listar a última revisión das páxinas recentemente modificadas da lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlist-example-props": "Buscar información adicional sobre a última revisión das páxinas modificadas recentemente da lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlist-example-allrev": "Buscar a información sobre todos os cambios recentes das páxinas da lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlist-example-generator": "Buscar a información de páxina das páxinas cambiadas recentemente da lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlist-example-generator-rev": "Buscar a información da revisión dos cambios recentes de páxinas na lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlist-example-wlowner": "Listar a última revisión das páxinas cambiadas recentemente da lista de vixiancia do usuario <kbd>Example</kbd>.",
+ "apihelp-query+watchlistraw-description": "Obter todas as páxinas da lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlistraw-param-namespace": "Só listar páxinas nestes espazos de nomes.",
+ "apihelp-query+watchlistraw-param-limit": "Cantos resultados totais mostrar por petición.",
+ "apihelp-query+watchlistraw-param-prop": "Que propiedades adicionais obter:\n;changed:Engade o selo de tempo da última notificación ó usuario dunha modificación.",
+ "apihelp-query+watchlistraw-param-show": "Só listar os elementos que cumplen estos criterios.",
+ "apihelp-query+watchlistraw-param-owner": "Usado con $1token para acceder á lista de páxinas de vixiancia doutro usuario.",
+ "apihelp-query+watchlistraw-param-token": "Identificador de seguridade (dispoñible nas [[Special:Preferences#mw-prefsection-watchlist|preferencias]] de usuario) para permitir o acceso a outros á súa páxina de vixiancia.",
+ "apihelp-query+watchlistraw-example-simple": "Listar páxinas na lista de vixiancia do usuario actual.",
+ "apihelp-query+watchlistraw-example-generator": "Buscar a información de páxina das páxinas da lista de vixiancia do usuario actual.",
+ "apihelp-revisiondelete-description": "Borrar e restaurar revisións.",
+ "apihelp-revisiondelete-param-type": "Tipo de borrado de revisión a ser tratada.",
+ "apihelp-revisiondelete-param-target": "Título de páxina para o borrado da revisión, se requerido para o tipo.",
+ "apihelp-revisiondelete-param-ids": "Identificadores para as revisións a ser borradas.",
+ "apihelp-revisiondelete-param-hide": "Que ocultar para cada revisión.",
+ "apihelp-revisiondelete-param-show": "Que mostrar para cada revisión.",
+ "apihelp-revisiondelete-param-suppress": "Eliminar os datos dos administradores así coma dos doutros.",
+ "apihelp-revisiondelete-param-reason": "Razón para o borrado ou restaurado.",
+ "apihelp-revisiondelete-example-revision": "Ocultar contido para revisión <kbd>12345</kbd> na <kbd>Páxina Principal</kbd>.",
+ "apihelp-revisiondelete-example-log": "Ocultar todos os datos da entrada de rexistro <kbd>67890</kbd> coa razón <kbd>BLP violation</kbd>.",
+ "apihelp-rollback-description": "Desfacer a última modificación da páxina.\n\nSe o último usuario que modificou a páxina fixo varias modificacións nunha fila, desfaranse todas.",
+ "apihelp-rollback-param-title": "Título da páxina a desfacer. Non pode usarse xunto con <var>$1pageid</var>.",
+ "apihelp-rollback-param-pageid": "ID da páxina a desfacer. Non pode usarse xunto con <var>$1title</var>.",
+ "apihelp-rollback-param-user": "Nome do usuario cuxas modificacións van a desfacerse.",
+ "apihelp-rollback-param-summary": "Personalizar o resumo de edición. Se está baleiro, usarase o resumo por defecto.",
+ "apihelp-rollback-param-markbot": "Marcar as edicións revertidas e a reversión como edicións de bot.",
+ "apihelp-rollback-param-watchlist": "Engadir ou eliminar sen condicións a páxina da lista de vixiancia do usuario actual, use as preferencias ou non cambie a vixiancia.",
+ "apihelp-rollback-example-simple": "Desfacer as últimas edicións á <kbd>Páxina Principal</kbd> do usuario <kbd>Exemplo</kbd>.",
+ "apihelp-rollback-example-summary": "Desfacer as últimas edicións á páxina <kbd>Main Page</kbd> polo usuario da dirección IP <kbd>192.0.2.5</kbd> co resumo de edición <kbd>Revertindo vandalismo</kbd>, marcar esas edicións e a reversión como edicións de bot.",
+ "apihelp-rsd-description": "Exportar un esquema RSD (Really Simple Discovery, Descubrimento Moi Simple).",
+ "apihelp-rsd-example-simple": "Exportar o esquema RSD.",
+ "apihelp-setnotificationtimestamp-description": "Actualiza o selo de tempo de notificación das páxinas vixiadas.\n\nIsto afecta ó resalte das páxinas modificadas na lista de vixiancia e historial, e o envío de correo cando a preferencia \"Mandarme un correo cando cambie unha páxina da miña lista de vixiancia\" está activa.",
+ "apihelp-setnotificationtimestamp-param-entirewatchlist": "Traballar en tódalas páxinas vixiadas.",
+ "apihelp-setnotificationtimestamp-param-timestamp": "Selo de tempo ó que fixar a notificación.",
+ "apihelp-setnotificationtimestamp-param-torevid": "Modificación á que fixar o selo de tempo de modificación (só unha páxina).",
+ "apihelp-setnotificationtimestamp-param-newerthanrevid": "Modificación na que fixar o selo de tempo de modificación máis recente (só unha páxina).",
+ "apihelp-setnotificationtimestamp-example-all": "Restaurar o estado de notificación para toda a páxina de vixiancia",
+ "apihelp-setnotificationtimestamp-example-page": "Restaurar o estado de notificación para a <kbd>Páxina Principal</kbd>.",
+ "apihelp-setnotificationtimestamp-example-pagetimestamp": "Fixar o selo de tempo de notificación para a <kbd>Main page</kbd> de forma que todas as edicións dende o 1 se xaneiro de 2012 queden sen revisar.",
+ "apihelp-setnotificationtimestamp-example-allpages": "Restaurar o estado de notificación para as páxinas no espazo de nomes de <kbd>{{ns:user}}</kbd>.",
+ "apihelp-tokens-description": "Obter os identificadores para accións de modificación de datos.\n\nEste módulo está obsoleto e foi reemprazado por [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].",
+ "apihelp-tokens-param-type": "Tipos de identificadores a consultar.",
+ "apihelp-tokens-example-edit": "Recuperar un identificador de modificación (por defecto).",
+ "apihelp-tokens-example-emailmove": "Recuperar un identificador de correo e un identificador de movemento.",
+ "apihelp-unblock-description": "Desbloquear un usuario.",
+ "apihelp-unblock-param-id": "ID do bloque a desbloquear (obtido de <kbd>list=blocks</kbd>). Non pode usarse xunto con <var>$1user</var>.",
+ "apihelp-unblock-param-user": "Nome de usuario, dirección IP ou rango de direccións IP a desbloquear. Non pode usarse xunto con <var>$1id</var>.",
+ "apihelp-unblock-param-reason": "Razón para desbloquear.",
+ "apihelp-unblock-example-id": "Desbloquear bloqueo ID #<kbd>105</kbd>.",
+ "apihelp-unblock-example-user": "Desbloquear usuario <kbd>Bob</kbd> con razón <kbd>Síntoo Bob</kbd>.",
+ "apihelp-undelete-description": "Restaurar modificacións dunha páxina borrada.\n\nUnha lista de modificacións borradas (incluíndo os seus selos de tempo) pode consultarse a través de [[Special:ApiHelp/query+deletedrevs|list=deletedrevs]], e unha lista de IDs de ficheiros borrados pode consultarse a través de [[Special:ApiHelp/query+filearchive|list=filearchive]].",
+ "apihelp-undelete-param-title": "Título da páxina a restaurar.",
+ "apihelp-undelete-param-reason": "Razón para restaurar.",
+ "apihelp-undelete-param-timestamps": "Selos de tempo das modificacións a restaurar. Se <var>$1timestamps</var> e <var>$1fileids</var> están baleiras, restaurarase todo.",
+ "apihelp-undelete-param-fileids": "IDs das modificacións de ficheiro a restaurar. Se <var>$1timestamps</var> e <var>$1fileids</var> están baleiras, serán restauradas todas.",
+ "apihelp-undelete-param-watchlist": "Engadir ou eliminar a páxina da lista de vixiancia do usuario actual sen condicións, use as preferencias ou non cambie a vixiancia.",
+ "apihelp-undelete-example-page": "Restaurar a <kbd>Páxina Principal</kbd>.",
+ "apihelp-undelete-example-revisions": "Restaurar dúas revisións de <kbd>[[Main Page]]</kbd>.",
+ "apihelp-upload-param-filename": "Nome de ficheiro obxectivo.",
+ "apihelp-upload-param-comment": "Subir comentario. Tamén usado como texto da páxina inicial para ficheiros novos se non se especifica <var>$1text</var>.",
+ "apihelp-upload-param-text": "Texto da páxina inicial para novos ficheiros.",
+ "apihelp-upload-param-watch": "Vixiar a páxina.",
+ "apihelp-upload-param-watchlist": "Engadir ou eliminar sen condicións a páxina da lista de vixiancia do usuario actual, use as preferencias ou non cambie a vixiancia.",
+ "apihelp-upload-param-ignorewarnings": "Ignorar as advertencias.",
+ "apihelp-upload-param-file": "Contido do ficheiro.",
+ "apihelp-upload-param-url": "URL onde buscar o ficheiro.",
+ "apihelp-upload-param-filekey": "Clave que identifica unha subida precedente e que foi almacenada temporalmente.",
+ "apihelp-upload-param-sessionkey": "Igual a $1filekey, mantido por razóns de compatibilidade con procesos antigos.",
+ "apihelp-upload-param-stash": "Se está indicado, o servidor almacenará o ficheiro temporalmente no canto de engadilo ó repositorio.",
+ "apihelp-upload-param-filesize": "Tamaño de ficheiro completo da carga.",
+ "apihelp-upload-param-offset": "Desaxuste do bloque en bytes.",
+ "apihelp-upload-param-chunk": "Contido do bloque.",
+ "apihelp-upload-param-async": "Facer de forma asíncrona as operacións de ficheiro potencialmente grandes cando sexa posible.",
+ "apihelp-upload-param-asyncdownload": "Facer de forma asíncrona a busca dunha URL.",
+ "apihelp-upload-param-leavemessage": "Se se usa asyncdownload, deixar unha mensaxe na páxina de conversa do usuario cando se remate.",
+ "apihelp-upload-param-statuskey": "Buscar o estado da subida para esta clave de ficheiro (subida por URL).",
+ "apihelp-upload-param-checkstatus": "Só buscar o estado da subida da clave de ficheiro indicada.",
+ "apihelp-upload-example-url": "Carga dunha URL",
+ "apihelp-upload-example-filekey": "Completar carga que fallou debido a avisos",
+ "apihelp-userrights-description": "Cambiar a pertencia dun usuario a un grupo.",
+ "apihelp-userrights-param-user": "Nome de usuario.",
+ "apihelp-userrights-param-userid": "ID de usuario.",
+ "apihelp-userrights-param-add": "Engadir o usuario a estes grupos.",
+ "apihelp-userrights-param-remove": "Eliminar o usuario destes grupos.",
+ "apihelp-userrights-param-reason": "Motivo para o cambio.",
+ "apihelp-userrights-example-user": "Engadir o usuario <kbd>FooBot</kbd> ó grupo <kbd>bot</kbd>, e eliminar dos grupos <kbd>sysop</kbd> e <kbd>burócrata</kbd>.",
+ "apihelp-userrights-example-userid": "Engadir ó usuario con ID <kbd>123</kbd> ó grupo <kbd>bot</kbd>, e borralo dos grupos <kbd>sysop</kbd> e <kbd>burócrata</kbd>.",
+ "apihelp-watch-description": "Engadir ou borrar páxinas da lista de vixiancia do usuario actual.",
+ "apihelp-watch-param-title": "Páxina a vixiar/deixar de vixiar. Usar no canto <var>$1titles</var>.",
+ "apihelp-watch-param-unwatch": "Se está definido, a páxina deixará de estar vixiada en vez de vixiada.",
+ "apihelp-watch-example-watch": "Vixiar a páxina <kbd>Páxina Principal</kbd>.",
+ "apihelp-watch-example-unwatch": "Deixar de vixiar a páxina <kbd>Páxina Principal</kbd>.",
+ "apihelp-watch-example-generator": "Vixiar as primeiras páxinas no espazo de nomes principal",
+ "apihelp-format-example-generic": "Formatar o resultado da consulta no formato $1.",
+ "apihelp-dbg-description": "Datos de saída en formato <code>var_export()</code> de PHP.",
+ "apihelp-dbgfm-description": "Datos de saída en formato <code>var_export()</code> de PHP(impresión en HTML).",
+ "apihelp-dump-description": "Datos de saída en formato PHP <code>var_dump()</code>.",
+ "apihelp-dumpfm-description": "Datos de saída en formato <code>var_dump()</code> de PHP(impresión en HTML).",
+ "apihelp-json-description": "Datos de saída en formato JSON.",
+ "apihelp-json-param-callback": "Se está especificado, inclúe a saída na chamada da función indicada. Para maior seguridade, todos os datos específicos do usuario serán restrinxidos.",
+ "apihelp-json-param-utf8": "Se está especificado, codifica a maioría (pero non todos) dos caracteres ASCII como UTF-8 no canto de reemprazalos con secuencias de escape hexadecimais.",
+ "apihelp-jsonfm-description": "Datos de saída en formato JSON(impresión en HTML).",
+ "apihelp-none-description": "Ningunha saída.",
+ "apihelp-php-description": "Datos de saída en formato serializado de PHP.",
+ "apihelp-phpfm-description": "Datos de saída en formato serializado de PHP(impresión en HTML).",
+ "apihelp-rawfm-description": "Datos de saída cos elementos de depuración en formato JSON(impresión en HTML).",
+ "apihelp-txt-description": "Datos de saída en formato PHP <code>print_r()</code>.",
+ "apihelp-txtfm-description": "Datos de saída en formato <code>print_r()</code> de PHP(impresión en HTML).",
+ "apihelp-wddx-description": "Datos de saída en formato WDDX.",
+ "apihelp-wddxfm-description": "Datos de saída en formato WDDX(impresión en HTML).",
+ "apihelp-xml-description": "Datos de saída en formato XML.",
+ "apihelp-xml-param-xslt": "Se está indicado, engade o nome da páxina como unha folla de estilo XSL. O valor debe ser un título no espazo de nomes {{ns:mediawiki}} rematando con <code>.xsl</code>.",
+ "apihelp-xml-param-includexmlnamespace": "Se está indicado, engade un espazo de nomes XML.",
+ "apihelp-xmlfm-description": "Datos de saída en formato XML(impresión en HTML).",
+ "apihelp-yaml-description": "Datos de saída en formato YAML.",
+ "apihelp-yamlfm-description": "Datos de saída en formato YAML(impresión en HTML).",
+ "api-format-title": "Resultado de API de MediaWiki",
+ "api-orm-param-props": "Campos a consultar.",
+ "api-orm-param-limit": "Número máximo de filas a mostrar.",
+ "api-pageset-param-titles": "Lista de títulos nos que traballar.",
+ "api-pageset-param-pageids": "Lista de identificadores de páxina nos que traballar.",
+ "api-pageset-param-revids": "Unha lista de IDs de modificacións sobre as que traballar.",
+ "api-pageset-param-generator": "Obter a lista de páxinas sobre as que traballar executando o módulo de consulta especificado.\n\n<strong>Nota:</strong>Os nomes de parámetro do xerador deben comezar cunha \"g\", vexa os exemplos.",
+ "api-pageset-param-redirects-generator": "Resolver automaticamente as redireccións en <var>$1titles</var>, <var>$1pageids</var>, e <var>$1revids</var>, e nas páxinas devoltas por <var>$1generator</var>.",
+ "api-pageset-param-redirects-nogenerator": "Resolver automaticamente as redireccións en <var>$1titles</var>, <var>$1pageids</var>, e <var>$1revids</var>.",
+ "api-pageset-param-converttitles": "Converter títulos a outras variantes se é preciso. Só funciona se a lingua de contido da wiki soporta a conversión en variantes. As linguas que soportan conversión en variante inclúen $1.",
+ "api-help-title": "Axuda da API de MediaWiki",
+ "api-help-lead": "Esta é unha páxina de documentación da API de MediaWiki xerada automaticamente.\n\nDocumentación e exemplos:\nhttps://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "Módulo principal",
+ "api-help-flag-deprecated": "Este módulo está obsoleto.",
+ "api-help-flag-internal": "<strong>Este módulo é interno ou inestable. </strong> O seu funcionamento pode cambiar sen aviso previo.",
+ "api-help-flag-readrights": "Este módulo precisa permisos de lectura.",
+ "api-help-flag-writerights": "Este módulo precisa permisos de escritura.",
+ "api-help-flag-mustbeposted": "Este módulo só acepta peticións POST.",
+ "api-help-flag-generator": "Este módulo pode usarse como xenerador.",
+ "api-help-parameters": "{{PLURAL:$1|Parámetro|Parámetros}}:",
+ "api-help-param-deprecated": "Obsoleto.",
+ "api-help-param-required": "Este parámetro é obrigatorio.",
+ "api-help-param-list": "{{PLURAL:$1|1=Un valor|2=Valores (separados con <kbd>{{!}}</kbd>)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Debe ser baleiro|Pode ser baleiro, ou $2}}",
+ "api-help-param-limit": "Non se permiten máis de $1.",
+ "api-help-param-limit2": "Non se permiten máis de $1 ($2 para bots).",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=O valor debe ser maior |2=Os valores deben ser maiores}} que $2.",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=O valor debe ser menor |2=Os valores deben ser menores}} que $3.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=O valor debe estar entre $2 e $3 |2=Os valores deben estar entre $2 e $3}}.",
+ "api-help-param-upload": "Debe ser enviado como un ficheiro importado usando multipart/form-data.",
+ "api-help-param-multi-separate": "Separe os valores con <kbd>|</kbd>.",
+ "api-help-param-multi-max": "O número máximo de valores é {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} para os bots).",
+ "api-help-param-default": "Por defecto: $1",
+ "api-help-param-default-empty": "Por defecto: <span class=\"apihelp-empty\">(baleiro)</span>",
+ "api-help-param-token": "Un identificador \"$1\" recuperado por [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+ "api-help-param-token-webui": "Por compatibilidade, o identificador usado na web UI tamén é aceptado.",
+ "api-help-param-disabled-in-miser-mode": "Desactivado debido ó [[mw:Manual:$wgMiserMode|modo minimal]].",
+ "api-help-param-limited-in-miser-mode": "<strong>Nota:</strong> Debido ó [[mw:Manual:$wgMiserMode|modo minimal]], usar isto pode devolver menos de <var>$1limit</var> resultados antes de seguir, en casos extremos, pode que non se devolvan resultados.",
+ "api-help-param-direction": "En que dirección enumerar:\n;newer:Lista os máis antigos primeiro. Nota: $1start ten que estar antes que $1end.\n;older:Lista os máis novos primeiro (por defecto). Nota: $1start ten que estar despois que $1end.",
+ "api-help-param-continue": "Cando estean dispoñibles máis resultados, use isto para continuar.",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(sen descrición)</span>",
+ "api-help-examples": "{{PLURAL:$1|Exemplo|Exemplos}}:",
+ "api-help-permissions": "{{PLURAL:$1|Permiso|Permisos}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Concedida a|Concedidas a}}: $2",
+ "api-help-right-apihighlimits": "Usar os valores superiores das consultas da API (consultas lentas: $1; consultas rápidas: $2). Os límites para as consultas lentas tamén se aplican ós parámetros multivaluados.",
+ "api-credits-header": "Créditos"
+}
diff --git a/includes/api/i18n/he.json b/includes/api/i18n/he.json
new file mode 100644
index 00000000..fcc7f7bd
--- /dev/null
+++ b/includes/api/i18n/he.json
@@ -0,0 +1,180 @@
+{
+ "@metadata": {
+ "authors": [
+ "Guycn2",
+ "Amire80",
+ "Inkbug",
+ "Danny-w",
+ "YaronSh",
+ "ערן"
+ ]
+ },
+ "apihelp-main-param-action": "איזו פעולה לבצע.",
+ "apihelp-main-param-format": "התבנית של הפלט.",
+ "apihelp-main-param-curtimestamp": "הכללת חותמת הזמן הנוכחית בתוצאה.",
+ "apihelp-block-description": "חסימת משתמש.",
+ "apihelp-block-param-user": "שם משתמש, כתובת IP, או טווח IP שהנך רוצה לחסום.",
+ "apihelp-block-param-reason": "סיבה לחסימה.",
+ "apihelp-compare-param-fromtitle": "כותרת ראשונה להשוואה.",
+ "apihelp-compare-param-fromid": "מס׳ זיהוי של העמוד הראשון להשוואה.",
+ "apihelp-compare-param-fromrev": "גרסה ראשונה להשוואה.",
+ "apihelp-compare-param-totitle": "כותרת שנייה להשוואה.",
+ "apihelp-compare-param-toid": "מס׳ מזהה של העמוד השני להשוואה.",
+ "apihelp-compare-param-torev": "גרסה שנייה להשוואה.",
+ "apihelp-compare-example-1": "יצירת תיעוד שינוי בין גרסה 1 ל־2.",
+ "apihelp-createaccount-description": "יצירת חשבון משתמש חדש.",
+ "apihelp-createaccount-param-name": "שם משתמש.",
+ "apihelp-createaccount-param-password": "ססמה (לא ישפיע אם הוגדר <var>$1mailpassword</var>).",
+ "apihelp-createaccount-param-domain": "שם מתחם לאימות חיצוני (רשות).",
+ "apihelp-createaccount-param-token": "אסימון יצירת חשבון הושג בבקשה הראשונה.",
+ "apihelp-createaccount-param-email": "כתובת הדוא״ל של המשתמש (רשות).",
+ "apihelp-createaccount-param-realname": "השם האמתי של המשתמש (רשות).",
+ "apihelp-createaccount-param-mailpassword": "אם הוגדר ערך כלשהו, תישלח ססמה אקראית אל המשתמש.",
+ "apihelp-createaccount-param-reason": "הסיבה כרשות ליצירת החשבון כפי שתופיע ברישומים.",
+ "apihelp-createaccount-param-language": "קוד השפה שיוגדר כבררת המחדל למשתמש (רשות, בררת המחדל היא שפת התוכן).",
+ "apihelp-createaccount-example-pass": "יצירת המשתמש <kbd>testuser</kbd> עם הססמה <kbd>test123</kbd>.",
+ "apihelp-createaccount-example-mail": "יצירת המשתמש <kbd>testmailuser</kbd> ושליחת ססמה שיוצרה אקראית בדוא״ל.",
+ "apihelp-delete-description": "מחיקת דף.",
+ "apihelp-delete-param-title": "כותרת העמוד למחיקה. לא ניתן להשתמש בשילוב עם <var>$1pageid</var>.",
+ "apihelp-delete-param-pageid": "מס׳ הזיהוי של העמוד למחיקה. לא ניתן להשתמש בשילוב עם <var>$1title</var>.",
+ "apihelp-delete-param-reason": "סיבת המחיקה. אם לא הוגדרה, תתווסף סיבה שנוצרה אוטומטית.",
+ "apihelp-delete-param-watch": "הוספת העמוד לרשימת המעקב של המשתמש הנוכחי.",
+ "apihelp-delete-param-unwatch": "הסרת הדף מרשימת המעקב של של המשתמש הנוכחי.",
+ "apihelp-delete-example-simple": "מחיקת הדף הראשי",
+ "apihelp-edit-param-text": "תוכן הדף.",
+ "apihelp-edit-param-minor": "עריכה משנית.",
+ "apihelp-edit-example-edit": "עריכת דף",
+ "apihelp-emailuser-description": "שליחת דוא\"ל למשתמש.",
+ "apihelp-expandtemplates-param-title": "כותרת הדף.",
+ "apihelp-feedcontributions-param-year": "החל משנה (ולפני כן).",
+ "apihelp-feedcontributions-param-month": "החל מחודש (ולפני כן).",
+ "apihelp-feedcontributions-param-tagfilter": "סינון תרומות בעלות התגיות הבאות.",
+ "apihelp-feedcontributions-param-deletedonly": "הצגת תרומות שנמחקו בלבד.",
+ "apihelp-feedcontributions-param-toponly": "הצגת עריכות שהן הגרסה העדכנית ביותר בלבד.",
+ "apihelp-feedcontributions-example-simple": "החזרת תרומות עבור המשתמש <kbd>Example</kbd>.",
+ "apihelp-feedrecentchanges-param-hideminor": "הסתרת שינוים משניים.",
+ "apihelp-feedrecentchanges-param-hidebots": "הסתרת שינויים שנעשו על ידי בוטים.",
+ "apihelp-feedrecentchanges-param-hideanons": "הסתרת שינויים שנעשו על ידי אנונימים.",
+ "apihelp-feedrecentchanges-param-hideliu": "הסתרת שינויים שנעשו על ידי משתמשים רשומים.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "הסתרת שינויים שנבדקו.",
+ "apihelp-feedrecentchanges-param-hidemyself": "הסתרת שינוים שנעשו על ידי המשתמש הנוכחי.",
+ "apihelp-feedrecentchanges-param-tagfilter": "סינון לפי תגית.",
+ "apihelp-feedrecentchanges-param-target": "הצגת שינויים שנעשו בדפים המקושרים לדף זה בלבד.",
+ "apihelp-feedrecentchanges-example-simple": "הצגת שינויים אחרונים.",
+ "apihelp-feedrecentchanges-example-30days": "הצגת שינויים אחרונים עבור 30 ימים.",
+ "apihelp-help-description": "הצגת עזרה עבור היחידות שצוינו.",
+ "apihelp-help-param-helpformat": "תסדיר פלט העזרה.",
+ "apihelp-help-param-toc": "לכלול תוכן עניינים בפלט HTML.",
+ "apihelp-import-param-xml": "קובץ XML שהועלה.",
+ "apihelp-login-param-name": "שם משתמש.",
+ "apihelp-login-param-password": "ססמה.",
+ "apihelp-login-param-domain": "שם מתחם (רשות).",
+ "apihelp-login-param-token": "אסימון כניסה התקבל בבקשה הראשונה.",
+ "apihelp-login-example-gettoken": "קבלת אסימון כניסה.",
+ "apihelp-login-example-login": "כניסה.",
+ "apihelp-logout-description": "יציאה וניקוי של נתוני הפעילות.",
+ "apihelp-logout-example-logout": "הוצאת המשתמש הנוכחי.",
+ "apihelp-managetags-description": "ביצוע פעולות ניהוליות הקשורות בשינוי תגיות.",
+ "apihelp-move-description": "העברת עמוד.",
+ "apihelp-opensearch-param-search": "מחרוזת לחיפוש.",
+ "apihelp-opensearch-param-namespace": "שמות מתחם לחיפוש.",
+ "apihelp-opensearch-param-format": "תסדיר הפלט.",
+ "apihelp-protect-example-protect": "הגנה על דף.",
+ "apihelp-query-param-list": "אילו רשימות לקבל.",
+ "apihelp-query+allcategories-description": "מניין של כל הקטגוריות.",
+ "apihelp-query+allcategories-param-from": "הקטגוריה ממנה להתחיל למנות.",
+ "apihelp-query+allimages-param-sha1": "גיבוב SHA1 של תמונה. דריסת $1sha1base36.",
+ "apihelp-query+allimages-param-sha1base36": "גיבוב SHA1 של התמונה בבסיס 36 (הבסיס בו נעשה שימוש במדיה־ויקי).",
+ "apihelp-query+allimages-param-limit": "כמה תמונות להחזיר בסך הכול.",
+ "apihelp-query+allimages-example-B": "הצגת רשימה של קבצים שמתחילים באות <kbd>B</kbd>.",
+ "apihelp-query+allimages-example-generator": "הצגת מידע על 4 קבצים המתחילים באות <kbd>T</kbd>.",
+ "apihelp-query+allmessages-param-prop": "אלו מאפיינים לקבל.",
+ "apihelp-query+allpages-param-limit": "כמה דפים להחזיר בסך הכול.",
+ "apihelp-query+allredirects-param-limit": "כמה פריטים להחזיר בסך הכול.",
+ "apihelp-query+categories-param-limit": "כמה קטגוריות להחזיר.",
+ "apihelp-query+categorymembers-param-startsortkey": "כדאי להשתמש ב־$1starthexsortkey במקום.",
+ "apihelp-query+categorymembers-param-endsortkey": "כדאי להשתמש ב־$1endhexsortkey במקום.",
+ "apihelp-query+categorymembers-example-simple": "קבלת עשרת העמודים הראשונים שתחת <kbd>קטגוריה:פיזיקה</kbd>.",
+ "apihelp-query+contributors-param-limit": "כמה תורמים להחזיר.",
+ "apihelp-query+contributors-example-simple": "הצגת תורמים לדף <kbd>עמוד ראשי</kbd>.",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|מצב|מצבים}}: $2",
+ "apihelp-query+duplicatefiles-param-limit": "כמה קבצים כפולים להחזיר.",
+ "apihelp-query+duplicatefiles-param-localonly": "חיפוש אחר קבצים במאגר המקומי בלבד.",
+ "apihelp-query+duplicatefiles-example-simple": "חיפוש אחר כפילויות של [[:קובץ:Albert Einstein Head.jpg]].",
+ "apihelp-query+duplicatefiles-example-generated": "חיפוש אחר כפילויות בין כל הקבצים.",
+ "apihelp-query+extlinks-param-limit": "כמה קישורים להחזיר.",
+ "apihelp-query+imageinfo-paramvalue-prop-comment": "תגובה על הגרסה.",
+ "apihelp-query+imageinfo-param-localonly": "חיפוש אחר קבצים במאגר המקומי בלבד.",
+ "apihelp-query+imageinfo-example-simple": "קבלת פרטים על הגרסה הנוכחית של [[:קובץ:Albert Einstein Head.jpg]].",
+ "apihelp-query+images-param-limit": "כמה קבצים להחזיר.",
+ "apihelp-query+info-paramvalue-prop-watchers": "מספר העוקבים, אם קיבלת הרשאה.",
+ "apihelp-query+info-paramvalue-prop-readable": "האם המשתמש יכול להציג דף זה.",
+ "apihelp-query+langlinks-param-title": "קישור לחיפוש. חובה להשתמש עם <var>$1lang</var>.",
+ "apihelp-query+links-description": "החזרת כל הקישורים מהדפים שצוינו.",
+ "apihelp-query+linkshere-param-limit": "כמה להחזיר.",
+ "apihelp-query+linkshere-param-show": "הצגת פריטים שתואמים את הדרישות הללו בלבד:\n;redirect:הצגת הפניות בלבד.\n;!redirect:הצגת קישורים שאינם הפניות בלבד.",
+ "apihelp-query+logevents-description": "קבלת אירועים מהרישומים.",
+ "apihelp-query+pageswithprop-param-dir": "באיזה כיוון לסדר.",
+ "apihelp-query+pageswithprop-example-simple": "הצגת עשרת הדפים הראשונים שעושים שימוש ב־<code>&#123;&#123;DISPLAYTITLE:&#125;&#125;</code>.",
+ "apihelp-query+pageswithprop-example-generator": "קבלת פרטיהם של עשרת הדפים הראשונים המשתמשים ב־<code>_&#95;NOTOC_&#95;</code>.",
+ "apihelp-query+prefixsearch-param-search": "מחרוזת לחיפוש.",
+ "apihelp-query+prefixsearch-param-limit": "מספר התוצאות המרבי להחזרה.",
+ "apihelp-query+prefixsearch-param-offset": "מספר תוצאות לדילוג.",
+ "apihelp-query+querypage-param-limit": "מספר תוצאות להחזרה.",
+ "apihelp-query+recentchanges-description": "מניית השינויים האחרונים.",
+ "apihelp-query+recentchanges-param-limit": "כמה שינויים להחזיר בסך הכול.",
+ "apihelp-query+recentchanges-param-type": "אילו סוגים של שינויים להציג.",
+ "apihelp-query+recentchanges-example-simple": "הצגת השינויים האחרונים.",
+ "apihelp-query+redirects-param-limit": "כמה הפניות להחזיר.",
+ "apihelp-query+revisions+base-param-limit": "הגבלת מספר הגרסאות שיוחזרו.",
+ "apihelp-query+tokens-example-types": "אחזור אסימון של רשימת המעקב ואסימון של ניטור",
+ "apihelp-xml-param-xslt": "אם צוין, יש להוסיף את שם הדף כגיליון עיצוב XSL. על הערך להיות כותרת ב {{ns:mediawiki}} במרחב שם המשתמש, המסתיים ב- <code>.xsl</code>.",
+ "api-format-title": "תוצאה של API של מדיה־ויקי",
+ "api-format-prettyprint-header": "זהו ייצוג ב־HTML של תסדיר $1. תסדיר HTML טוב לתיקון שגיאות, אבל אינו מתאים ליישומים.\n\nיש לציין את הפרמטר format כדי לשנות את תסדיר הפלט. כדי לראות ייצוג של תסדיר $1 לא ב־HTML יש לרשום format=$2.\n\nר' את [https://www.mediawiki.org/wiki/API התיעוד המלא], או את [[Special:ApiHelp/main|העזרה של API]] למידע נוסף.",
+ "api-orm-param-props": "באילו שדות לעשות שאילתה.",
+ "api-orm-param-limit": "מספר מרבי של שורות להחזיר.",
+ "api-pageset-param-titles": "רשימת כותרות.",
+ "api-pageset-param-pageids": "רשימת מזהי דף לעובד עליהם.",
+ "api-pageset-param-revids": "רשימת מזהי גרסה לעבוד עליהם.",
+ "api-pageset-param-generator": "קבלת רשימת דפים לעבוד עליהם על־ידי הרצת יחידת שאילתה שצוינה.\n\n'''לתשומת לבך:''' לשמות בפרמטר generator צריכה להיות התחילית \"g\", ר' דוגמאות.",
+ "api-pageset-param-redirects-generator": "פתרון אוטומטי של הפניות ב־$1titles, ב־$1pageids, וב־$1revids, ודפים שמחזיר $1generator.",
+ "api-pageset-param-redirects-nogenerator": "פתרון אוטומטי של הפניות ב־$1titles, ב־$1pageids וב־$1revids.",
+ "api-pageset-param-converttitles": "המרת כותרות לסוגי כתב אחרים אם זה נחוץ. זה עובד רק אם שפת הכותרת של הוויקי תומכת בהמרת סוגי כתב. השפות שתמכות בהמרת סוגי כתב הן $1.",
+ "api-help-title": "עזרה של MediaWiki API",
+ "api-help-lead": "זהו דף תיעוד של API שנוצר באופן אוטומטי.\n\nתיעוד ודוגמאות: https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "יחידה ראשית",
+ "api-help-flag-deprecated": "יחידה זו אינה מומלצת לשימוש.",
+ "api-help-flag-internal": "<strong>יחידה זו היא פנימית או לא יציבה.</strong>\nהפעולה שלה עשויה להשתנות ללא הודעה מוקדמת.",
+ "api-help-flag-readrights": "יחידה זו דורשת הרשאות קריאה.",
+ "api-help-flag-writerights": "יחידה זו דורשת הרשאות כתיבה.",
+ "api-help-flag-mustbeposted": "יחידה זו מקבלת רק בקשות POST.",
+ "api-help-flag-generator": "היחידה הזאת יכולה להיות מחולל.",
+ "api-help-parameters": "{{PLURAL:$1|פרמטר|פרמטרים}}:",
+ "api-help-param-deprecated": "מיושן.",
+ "api-help-param-required": "פרמטר זה נדרש.",
+ "api-help-param-list": "{{PLURAL:$1|1=ערך אחד|2=ערכים (מופרדים באמצעות \"{{!}}\")}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=חייב להיות ריק|יכול להיות ריק או $2}}",
+ "api-help-param-limit": "מספר הפרמטרים לא יכול להיות גדול מ־$1.",
+ "api-help-param-limit2": "המספר המרבי המותר הוא $1 (עבור בוטים – $2).",
+ "api-help-param-integer-min": "ה{{PLURAL:$1|1=ערך|2=ערכים}} לא יכולים להיות קטנים מ־$2.",
+ "api-help-param-integer-max": "ה{{PLURAL:$1|1=ערך לא יכול להיות גדול|2=ערכים לא יכולים להיות גדולים}} מ־$3.",
+ "api-help-param-integer-minmax": "ה{{PLURAL:$1|1=ערך חייב|2=ערכים חייבים}} להיות בין $2 ל־$3.",
+ "api-help-param-upload": "חייב להישלח (posted) בתור העלאת קובץ באמצעות multipart/form-data.",
+ "api-help-param-multi-separate": "הפרדה בין ערכים נעשית באמצעות <kbd>|</kbd>",
+ "api-help-param-multi-max": "מספר הערכים המרבי הוא {{PLURAL:$1|$1}} (עבור בוטים – {{PLURAL:$2|$2}}).",
+ "api-help-param-default": "ברירת מחדל: $1",
+ "api-help-param-default-empty": "ברירת מחדל: <span class=\"apihelp-empty\">(ריק)</span>",
+ "api-help-param-token": "אסימון \"$1\" אוחזר מ־[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+ "api-help-param-token-webui": "לשם תאימות, גם האסימון שמשמש בממשק דפדפן מתקבל.",
+ "api-help-param-disabled-in-miser-mode": "כבוי בשל [https://www.mediawiki.org/wiki/Manual:$wgMiserMode מצב חיסכון].",
+ "api-help-param-limited-in-miser-mode": "'''לתשומת לבך:''' בשל [https://www.mediawiki.org/wiki/Manual:$wgMiserMode מצב חיסכון], שימוש בזה יכול להוביל לפחות מ־\"$1limit\" תוצאות לפני המשך; במצבים קיצוניים ייתכן שיחזרו אפס תוצאות.",
+ "api-help-param-direction": "באיזה כיוון למספר:\n;newer:לרשום את הישנים ביותר בהתחלה. לתשומת לבך: $1start חייב להיות לפני $1end.\n;older:לרשום את החדשים ביותר בהתחלה (בררת מחדל). לתשומת לבך: $1start חייב להיות אחרי $1end.",
+ "api-help-param-continue": "כשיש עוד תוצאות, להשתמש בזה בשביל להמשיך.",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(ללא תיאור)</span>",
+ "api-help-examples": "{{PLURAL:$1|דוגמה|דוגמאות}}:",
+ "api-help-permissions": "{{PLURAL:$1|הרשאה|הרשאות}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|הוענק ל|הוענקו ל}}: $2",
+ "api-help-right-apihighlimits": "להשתמש במגבלות גבוהות יותר בשאילתות API (שאילתות אטיות: $1; שאילתות מהירות: $2). המגבלות לשאילתות אטיות חלות גם על פרמטרים מרובי־ערכים.",
+ "api-credits-header": "קרדיטים",
+ "api-credits": "מפתחי ה־API:\n* רואן קטאו (מפתח מוביל 2007–2009)\n* ויקטור וסילייב\n* בריאן טונג מין\n* סאם ריד\n* יורי אסטרחן (יוצר, מפתח מוביל מספטמבר 2006 עד ספטמבר 2007)\n* בראד יורש (מפתח מוביל מאז 2013)\n\nאנא שלחו הערות, הצעות ושאלות לכתובת mediawiki-api@lists.wikimedia.org או כתבו דיווח באג באתר https://bugzilla.wikimedia.org."
+}
diff --git a/includes/api/i18n/hsb.json b/includes/api/i18n/hsb.json
new file mode 100644
index 00000000..b25ae9b2
--- /dev/null
+++ b/includes/api/i18n/hsb.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "J budissin"
+ ]
+ },
+ "apihelp-main-param-format": "Wudawanski format"
+}
diff --git a/includes/api/i18n/hu.json b/includes/api/i18n/hu.json
new file mode 100644
index 00000000..516d8c77
--- /dev/null
+++ b/includes/api/i18n/hu.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Csega",
+ "Dorgan"
+ ]
+ },
+ "apihelp-block-description": "Szerkesztő blokkolása",
+ "apihelp-block-param-reason": "Blokkolás oka.",
+ "apihelp-block-param-nocreate": "Új regisztráció megakadályozása",
+ "apihelp-createaccount-param-name": "Felhasználónév.",
+ "apihelp-delete-description": "Lap törlése.",
+ "apihelp-delete-example-simple": "Kezdőlap törlése.",
+ "apihelp-edit-example-edit": "Lap szerkesztése",
+ "apihelp-expandtemplates-param-title": "Lap címe.",
+ "apihelp-userrights-param-userid": "Felhasználói azonosító."
+}
diff --git a/includes/api/i18n/ia.json b/includes/api/i18n/ia.json
new file mode 100644
index 00000000..de9d65fe
--- /dev/null
+++ b/includes/api/i18n/ia.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "McDutchie"
+ ]
+ },
+ "apihelp-main-param-action": "Qual action exequer.",
+ "apihelp-main-param-format": "Le formato del resultato.",
+ "apihelp-main-param-maxlag": "Le latentia maximal pote esser usate quando MediaWiki es installate in un cluster de base de datos replicate. Pro evitar actiones que causa additional latentia de replication de sito, iste parametro pote facer le cliente attender usque le latentia de replication es minus que le valor specificate. In caso de latentia excessive, le codice de error \"maxlag\" es retornate con un message como \"Attende $host: $lag secundas de latentia\".<br />Vide https://www.mediawiki.org/wiki/Manual:Maxlag_parameter pro plus information.",
+ "apihelp-main-param-smaxage": "Fixar le capite <code>s-maxage</code> a iste numero de secundas. Errores nunquam es mittite in cache.",
+ "apihelp-main-param-maxage": "Fixar le capite <code>max-age</code> a iste numero de secundas. Errores nunquam es mittite in cache.",
+ "apihelp-main-param-assert": "Verificar si le usator ha aperite session si mittite a \"user\", o si ha le derecto de usator robot si \"bot\".",
+ "apihelp-main-param-requestid": "Omne valor fornite hic essera includite in le responsa. Pote esser usate pro distinguer requestas.",
+ "apihelp-main-param-servedby": "Includer in le resultato le nomine del host que ha servite le requesta.",
+ "apihelp-main-param-curtimestamp": "Includer le data e hora actual in le resultato.",
+ "apihelp-main-param-origin": "Quando acceder al API usante un requesta AJAX inter-dominios (CORS), mitte isto al dominio de origine. Isto debe esser includite in omne requesta pre-flight, e dunque debe facer parte del URI del requesta (e non del corpore POST). Isto debe corresponder exactemente a un del origines in le capite Origin:, dunque illo debe esser mittite a qualcusa como http://ia.wikipedia.org o https://meta.wikimedia.org. Si iste parametro non corresponde al capite Origin:, un responsa 403 essera retornate. Si iste parametro corresponde al capite Origin: e le origine es in le lista blanc, un capite Access-Control-Allow-Origin essera inserite.",
+ "apihelp-main-param-uselang": "Lingua a usar pro traductiones de messages. Un lista de codices pote esser obtenite ab [[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]] con siprop=languages, o specifica \"user\" pro usar le preferentia de lingua del usator actual, o specifica \"content\" pro usar le lingua de contento de iste wiki.",
+ "apihelp-block-description": "Blocar un usator.",
+ "apihelp-block-param-user": "Nomine de usator, adresse IP o intervallo IP que tu vole blocar.",
+ "apihelp-block-param-expiry": "Tempore de expiration. Pote esser relative (p.ex. \"5 months\" o \"2 weeks\") o absolute (p.ex. \"2014-09-18T12:34:56Z\"). Si es mittite a \"infinite\", \"indefinite\" o \"never\", le blocada nunquam expirara.",
+ "apihelp-block-param-reason": "Motivo del blocada.",
+ "apihelp-block-param-anononly": "Blocar solmente usatores anonyme (i.e. disactivar modificationes anonyme pro iste adresse IP).",
+ "apihelp-block-param-nocreate": "Impedir le creation de contos.",
+ "apihelp-block-param-autoblock": "Blocar automaticamente le adresse IP usate le plus recentemente, e omne IPs successive desde le quales ille/-a tenta facer modificationes.",
+ "apihelp-block-param-noemail": "Impedir que le usator invia e-mail per le wiki. (Require le derecto \"blockemail\").",
+ "apihelp-query+revisions-example-first5-not-localhost": "Obtener le prime 5 versiones del \"Pagina principal\" que non ha essite facite per le usator anonyme \"127.0.0.1\"",
+ "api-credits": "Programmatores del API:\n* Roan Kattouw (programmator dirigente Sept. 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (creator, programmator dirigente Sept. 2006–Sept. 2007)\n* Brad Jorsch (programmator dirigente 2013–presente)\n\nInvia tu commentos, suggestiones e questiones a mediawiki-api@lists.wikimedia.org\no insere un reportage de bug a https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/it.json b/includes/api/i18n/it.json
new file mode 100644
index 00000000..68f4e40a
--- /dev/null
+++ b/includes/api/i18n/it.json
@@ -0,0 +1,31 @@
+{
+ "@metadata": {
+ "authors": [
+ "Beta16",
+ "Nivit",
+ "Toadino2"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentazione (in inglese)]]\n* [[mw:API:FAQ|FAQ (in inglese)]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Mailing list]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annunci sull'API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bug & richieste]\n</div>\n<strong>Stato:</strong> Tutte le funzioni e caratteristiche mostrate su questa pagina dovrebbero funzionare, ma l'API è ancora in fase d'attivo sviluppo, e potrebbe cambiare in qualsiasi momenento. Iscriviti alla [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce mailing list] per essere informato sugli aggiornamenti.\n\n<strong>Istruzioni sbagliate:</strong> quando vengono impartite all'API delle istruzioni sbagliate, un'intestazione HTTP verrà inviata col messaggio \"MediaWiki-API-Error\" e sia al valore dell'intestazione sia al codice d'errore verrà impostato lo stesso valore. Per maggiori informazioni leggi [[mw:API:Errors_and_warnings|API:Errori ed avvertimenti (in inglese)]].",
+ "apihelp-main-param-action": "Azione da compiere.",
+ "apihelp-main-param-format": "Formato dell'output.",
+ "apihelp-main-param-assert": "Verifica che l'utente sia loggato se si è impostato <kbd>utente</kbd>, o che abbia i permessi di bot se si è impostato <kbd>bot</kbd>.",
+ "apihelp-main-param-requestid": "Tutti i valori forniti saranno implementati nella risposta. Potrebbero venir utilizzati per distinguere le richieste.",
+ "apihelp-block-description": "Blocca un utente.",
+ "apihelp-block-param-reason": "Motivo del blocco.",
+ "apihelp-emailuser-description": "Manda un'e-mail ad un utente.",
+ "apihelp-emailuser-param-ccme": "Mandami una copia di questa mail.",
+ "apihelp-expandtemplates-description": "Espandi tutti i template nel wikitesto.",
+ "apihelp-expandtemplates-param-title": "Titolo della pagina.",
+ "apihelp-expandtemplates-param-text": "Wikitesto da convertire.",
+ "apihelp-query+recentchanges-example-simple": "Elenco modifiche recenti.",
+ "apihelp-upload-example-url": "Carica da un URL.",
+ "api-help-parameters": "{{PLURAL:$1|Parametro|Parametri}}:",
+ "api-help-param-deprecated": "Deprecato.",
+ "api-help-param-required": "Questo parametro è obbligatorio.",
+ "api-help-param-default": "Predefinito: $1",
+ "api-help-param-default-empty": "Predefinito: <span class=\"apihelp-empty\">(vuoto)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(nessuna descrizione)</span>",
+ "api-help-examples": "{{PLURAL:$1|Esempio|Esempi}}:",
+ "api-credits-header": "Crediti"
+}
diff --git a/includes/api/i18n/ja.json b/includes/api/i18n/ja.json
new file mode 100644
index 00000000..3940469a
--- /dev/null
+++ b/includes/api/i18n/ja.json
@@ -0,0 +1,213 @@
+{
+ "@metadata": {
+ "authors": [
+ "Shirayuki",
+ "2nd-player",
+ "Los688",
+ "Whym",
+ "Mfuji"
+ ]
+ },
+ "apihelp-main-param-action": "実行する操作です。",
+ "apihelp-main-param-format": "出力する形式です。",
+ "apihelp-main-param-smaxage": "<code>s-maxage</code> ヘッダーにこの秒数を設定します。エラーがキャッシュされることはありません。",
+ "apihelp-main-param-maxage": "<code>max-age</code> ヘッダーにこの秒数を設定します。エラーがキャッシュされることはありません。",
+ "apihelp-main-param-assert": "<kbd>user</kbd> を設定した場合は利用者がログイン済みかどうかを、<kbd>bot</kbd> を指定した場合はボット権限があるかどうかを、それぞれ検証します。",
+ "apihelp-main-param-requestid": "任意の値を指定でき、その値が結果に含められます。リクエストを識別するために使用できます。",
+ "apihelp-main-param-servedby": "リクエストを処理したホスト名を結果に含めます。",
+ "apihelp-main-param-curtimestamp": "現在のタイムスタンプを結果に含めます。",
+ "apihelp-main-param-uselang": "メッセージの翻訳に使用する言語です。コードの一覧は <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> に <kbd>siprop=languages</kbd> を付けることで取得できます。<kbd>user</kbd> を指定することで現在の利用者の個人設定の言語を、<kbd>content</kbd> を指定することでこのウィキの本文の言語を使用することもできます。",
+ "apihelp-block-description": "利用者をブロックします。",
+ "apihelp-block-param-user": "ブロックする利用者名、IPアドレスまたはIPレンジ。",
+ "apihelp-block-param-reason": "ブロックの理由。",
+ "apihelp-block-param-anononly": "匿名利用者のみブロックします(つまり、このIPアドレスからの匿名での編集を不可能にします)。",
+ "apihelp-block-param-nocreate": "アカウントの作成を禁止します。",
+ "apihelp-block-param-autoblock": "その利用者が最後に使用したIPアドレスと、ブロック後に編集を試みた際のIPアドレスを自動的にブロックします。",
+ "apihelp-block-param-noemail": "Wikiを通して電子メールを送信することを禁止します。(<code>blockemail</code> 権限が必要です)",
+ "apihelp-block-param-hidename": "ブロック記録から利用者名を秘匿します。(<code>hideuser</code> 権限が必要です)",
+ "apihelp-block-param-reblock": "その利用者がすでにブロックされている場合、ブロックを上書きします。",
+ "apihelp-block-param-watchuser": "その利用者またはIPアドレスの利用者ページとトークページをウォッチします。",
+ "apihelp-block-example-ip-simple": "IPアドレス <kbd>192.0.2.5</kbd> を <kbd>First strike<kbd> という理由で3日ブロックする",
+ "apihelp-block-example-user-complex": "利用者 <kbd>Vandal</kbd> を <kbd>Vandalism</kbd> という理由で無期限ブロックし、新たなアカウント作成とメールの送信を禁止する。",
+ "apihelp-checktoken-param-type": "調べるトークンの種類。",
+ "apihelp-checktoken-param-token": "調べるトークン。",
+ "apihelp-checktoken-example-simple": "<kbd>csrf</kbd> トークンの妥当性を調べる。",
+ "apihelp-compare-example-1": "版1と2の差分を生成する。",
+ "apihelp-createaccount-description": "新しい利用者アカウントを作成します。",
+ "apihelp-createaccount-param-name": "利用者名。",
+ "apihelp-createaccount-param-password": "パスワード (<var>$1mailpassword</var> が設定されると無視されます)。",
+ "apihelp-createaccount-param-token": "最初のリクエストで得られたアカウント作成用トークンです。",
+ "apihelp-createaccount-param-email": "利用者の電子メールアドレス (任意)。",
+ "apihelp-createaccount-param-realname": "利用者の本名 (省略可能)。",
+ "apihelp-createaccount-param-mailpassword": "設定されると (その値を問わず)、ランダムなパスワードがその利用者に電子メールで送られます。",
+ "apihelp-createaccount-param-reason": "ログに記録されるアカウント作成の理由 (任意)。",
+ "apihelp-createaccount-example-pass": "利用者 <kbd>testuser</kbd> をパスワード <kbd>test123</kbd> として作成する。",
+ "apihelp-createaccount-example-mail": "利用者 <kbd>testuser</kbd>を作成し、ランダムに生成されたパスワードをメールで送る",
+ "apihelp-delete-description": "ページを削除します。",
+ "apihelp-delete-param-title": "削除するページ名です。<var>$1pageid</var> とは同時に使用できません。",
+ "apihelp-delete-param-pageid": "削除するページIDです。<var>$1title</var> とは同時に使用できません。",
+ "apihelp-delete-param-reason": "削除の理由です。入力しない場合、自動的に生成された理由が使用されます。",
+ "apihelp-delete-param-watch": "そのページを現在の利用者のウォッチリストに追加します。",
+ "apihelp-delete-param-unwatch": "そのページを現在の利用者のウォッチリストから除去します。",
+ "apihelp-delete-example-simple": "<kbd>Main Page</kbd> を削除する",
+ "apihelp-delete-example-reason": "<kbd>Preparing for move</kbd> という理由で <kbd>Main Page</kbd> を削除する",
+ "apihelp-disabled-description": "このモジュールは無効化されています。",
+ "apihelp-edit-description": "ページを作成、編集します。",
+ "apihelp-edit-param-title": "編集するページ名です。<var>$1pageid</var> とは同時に使用できません。",
+ "apihelp-edit-param-pageid": "編集するページIDです。<var>$1title</var> とは同時に使用できません。",
+ "apihelp-edit-param-section": "節番号です。先頭の節の場合は <kbd>0</kbd>、新しい節の場合は <kbd>new</kbd>を指定します。",
+ "apihelp-edit-param-sectiontitle": "新しい節の名前です。",
+ "apihelp-edit-param-text": "ページの本文。",
+ "apihelp-edit-param-minor": "細部の編集",
+ "apihelp-edit-param-createonly": "すでにそのページが存在する場合は編集を行いません。",
+ "apihelp-edit-param-nocreate": "そのページが存在しない場合にエラーを返します。",
+ "apihelp-edit-param-watch": "そのページを現在の利用者のウォッチリストに追加します。",
+ "apihelp-edit-param-unwatch": "そのページを現在の利用者のウォッチリストから除去します。",
+ "apihelp-edit-param-token": "このトークンは常に最後のパラメーターとして、または少なくとも $1text パラメーターより後に送信されるべきです。",
+ "apihelp-edit-example-edit": "ページを編集",
+ "apihelp-emailuser-description": "利用者に電子メールを送信します。",
+ "apihelp-emailuser-param-target": "送信先の利用者名。",
+ "apihelp-emailuser-param-text": "電子メールの本文。",
+ "apihelp-emailuser-param-ccme": "電子メールの複製を自分にも送信します。",
+ "apihelp-emailuser-example-email": "利用者 <kbd>WikiSysop</kbd> に <kbd>Content</kbd> という本文の電子メールを送信。",
+ "apihelp-expandtemplates-description": "ウィキテキストに含まれるすべてのテンプレートを展開します。",
+ "apihelp-expandtemplates-param-title": "ページの名前です。",
+ "apihelp-expandtemplates-param-text": "変換するウィキテキストです。",
+ "apihelp-expandtemplates-example-simple": "ウィキテキスト <kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd> を展開する。",
+ "apihelp-feedcontributions-param-deletedonly": "削除された投稿記録のみ表示します。",
+ "apihelp-feedcontributions-param-toponly": "最新版の編集のみ表示します。",
+ "apihelp-feedcontributions-param-newonly": "ページ作成を伴う編集のみを表示します。",
+ "apihelp-feedcontributions-example-simple": "利用者 <kbd>Example</kbd> の投稿記録を取得する。",
+ "apihelp-feedrecentchanges-param-limit": "返す結果の最大数。",
+ "apihelp-feedrecentchanges-param-hideminor": "細部の変更を隠す。",
+ "apihelp-feedrecentchanges-param-hidebots": "ボットによる変更を隠す。",
+ "apihelp-feedrecentchanges-param-hideanons": "未登録利用者による変更を隠す。",
+ "apihelp-feedrecentchanges-param-hideliu": "登録利用者による変更を隠す。",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "巡回済みの変更を隠す。",
+ "apihelp-feedrecentchanges-example-simple": "最近の更新を表示する。",
+ "apihelp-feedrecentchanges-example-30days": "最近30日間の変更を表示する。",
+ "apihelp-filerevert-example-revert": "<kbd>Wiki.png</kbd> を <kbd>2011-03-05T15:27:40Z</kbd> の版に差し戻す。",
+ "apihelp-help-description": "指定したモジュールのヘルプを表示します。",
+ "apihelp-help-param-modules": "ヘルプを表示するモジュールです (<var>action</var> パラメーターおよび <var>format</var> パラメーターの値、または <kbd>main</kbd>)。<kbd>+</kbd> を使用して下位モジュールを指定できます。",
+ "apihelp-help-param-submodules": "指定したモジュールの下位モジュールのヘルプを含めます。",
+ "apihelp-help-param-recursivesubmodules": "下位モジュールのヘルプを再帰的に含めます。",
+ "apihelp-help-param-helpformat": "ヘルプの出力形式です。",
+ "apihelp-help-param-toc": "HTML 出力に目次を含めます。",
+ "apihelp-help-example-main": "メイン モジュールのヘルプ",
+ "apihelp-help-example-recursive": "すべてのヘルプを1つのページに",
+ "apihelp-help-example-help": "ヘルプ モジュール自身のヘルプ",
+ "apihelp-help-example-query": "2つの下位モジュールのヘルプ",
+ "apihelp-imagerotate-example-simple": "<kbd>File:Example.png</kbd> を <kbd>90</kbd> 度回転させる。",
+ "apihelp-imagerotate-example-generator": "<kbd>Category:Flip</kbd> 内のすべての画像を <kbd>180</kbd> 度回転させる。",
+ "apihelp-import-param-xml": "XMLファイルをアップロード",
+ "apihelp-import-param-rootpage": "このページの下位ページとしてインポートする。",
+ "apihelp-import-example-import": "[[meta:Help:Parserfunctions]] をすべての履歴とともに名前空間100 にインポートする。",
+ "apihelp-login-param-name": "利用者名。",
+ "apihelp-login-param-password": "パスワード。",
+ "apihelp-login-param-token": "最初のリクエストで取得したログイントークンです。",
+ "apihelp-login-example-gettoken": "ログイントークンを取得する。",
+ "apihelp-login-example-login": "ログイン",
+ "apihelp-logout-description": "ログアウトしてセッションデータを消去します。",
+ "apihelp-logout-example-logout": "現在の利用者をログアウトする。",
+ "apihelp-move-description": "ページを移動します。",
+ "apihelp-move-param-from": "移動するページのページ名です。<var>$1fromid</var> とは同時に使用できません。",
+ "apihelp-move-param-fromid": "移動するページのページIDです。<var>$1from</var> とは同時に使用できません。",
+ "apihelp-move-param-to": "移動後のページ名。",
+ "apihelp-move-param-reason": "名称変更の理由。",
+ "apihelp-move-param-movetalk": "存在する場合、トークページも名前を変更します。",
+ "apihelp-move-param-movesubpages": "可能であれば、下位ページも名前を変更します。",
+ "apihelp-move-param-noredirect": "転送ページを作成しません。",
+ "apihelp-move-param-watch": "そのページと転送ページを現在の利用者のウォッチリストに追加します。",
+ "apihelp-move-param-unwatch": "そのページと転送ページを現在の利用者のウォッチリストから除去します。",
+ "apihelp-move-param-ignorewarnings": "あらゆる警告を無視",
+ "apihelp-move-example-move": "<kbd>Badtitle</kbd> を <kbd>Goodtitle</kbd> に転送ページを残さず移動",
+ "apihelp-opensearch-param-search": "検索文字列。",
+ "apihelp-opensearch-param-limit": "返す結果の最大数。",
+ "apihelp-opensearch-param-namespace": "検索する名前空間。",
+ "apihelp-opensearch-param-suggest": "<var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> が false の場合、何もしません。",
+ "apihelp-paraminfo-description": "API モジュールに関する情報を取得します。",
+ "apihelp-patrol-description": "ページまたは版を巡回済みにします。",
+ "apihelp-patrol-param-revid": "巡回済みにする版ID。",
+ "apihelp-patrol-example-rcid": "最近の更新を巡回",
+ "apihelp-protect-description": "ページの保護レベルを変更します。",
+ "apihelp-protect-param-title": "保護(解除)するページ名です。$1pageid とは同時に指定できません。",
+ "apihelp-protect-param-pageid": "保護(解除)するページIDです。$1title とは同時に指定できません。",
+ "apihelp-protect-param-expiry": "有効期限です。タイムスタンプがひとつだけ指定された場合は、それがすべての保護に適用されます。無期限の保護を行う場合は<kbd>infinite</kbd>, <kbd>indefinite</kbd>, <kbd>infinity</kbd>, または <kbd>never</kbd> を指定します。",
+ "apihelp-protect-param-reason": "保護(解除)の理由。",
+ "apihelp-protect-param-watch": "指定されると、保護(解除)するページが現在の利用者のウォッチリストに追加されます。",
+ "apihelp-protect-example-protect": "ページを保護する。",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "<var>$3user</var> と同時に指定できません。",
+ "apihelp-query+alldeletedrevisions-param-user": "この利用者による版のみを一覧表示する。",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "この利用者による版を一覧表示しない。",
+ "apihelp-query+alldeletedrevisions-param-namespace": "この名前空間に含まれるページのみを一覧表示します。",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "標準名前空間にある削除された最初の50版を一覧表示する。",
+ "apihelp-query+categorymembers-example-simple": "<kbd>Category:Physics</kbd> に含まれる最初の10ページを取得する。",
+ "apihelp-query+categorymembers-example-generator": "<kbd>Category:Physics</kbd> に含まれる最初の10ページのページ情報を取得する。",
+ "apihelp-query+contributors-example-simple": "<kbd>Main Page</kbd> への投稿者を表示する。",
+ "apihelp-query+deletedrevisions-param-user": "この利用者による版のみを一覧表示。",
+ "apihelp-query+deletedrevisions-param-excludeuser": "この利用者による版を一覧表示しない。",
+ "apihelp-query+deletedrevisions-param-limit": "一覧表示する版の最大数。",
+ "apihelp-query+deletedrevisions-example-titles": "ページ <kbd>Main Page</kbd> および <kbd>Talk:Main Page</kbd> の削除された版とその内容を一覧表示する。",
+ "apihelp-query+deletedrevisions-example-revids": "削除された版 <kbd>123456</kbd> に関する情報を一覧表示する。",
+ "apihelp-query+info-description": "ページの基本的な情報を取得します。",
+ "apihelp-query+info-paramvalue-prop-protection": "それぞれのページの保護レベルを一覧表示する。",
+ "apihelp-query+info-example-simple": "<kbd>Main Page</kbd> に関する情報を取得する。",
+ "apihelp-query+iwbacklinks-example-simple": "[[wikibooks:Test]] へリンクしているページを取得する。",
+ "apihelp-query+iwbacklinks-example-generator": "[[wikibooks:Test]] へリンクしているページの情報を取得する。",
+ "apihelp-query+langbacklinks-example-simple": "[[:fr:Test]] へリンクしているページを取得する。",
+ "apihelp-query+langbacklinks-example-generator": "[[:fr:Test]] へリンクしているページの情報を取得する。",
+ "apihelp-format-example-generic": "クエリの結果を $1 形式に整形します",
+ "apihelp-dbg-description": "データを PHP の <code>var_export()</code> 形式で出力します。",
+ "apihelp-dbgfm-description": "データを PHP の <code>var_export()</code> 形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-dump-description": "データを PHP の <code>var_dump()</code> 形式で出力します。",
+ "apihelp-dumpfm-description": "データを PHP の <code>var_dump()</code> 形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-json-description": "データを JSON 形式で出力します。",
+ "apihelp-json-param-callback": "指定すると、指定した関数呼び出しで出力をラップします。安全のため、利用者固有のデータはすべて制限されます。",
+ "apihelp-json-param-utf8": "指定すると、大部分の非 ASCII 文字 (すべてではありません) を、16 進のエスケープ シーケンスに置換する代わりに UTF-8 として符号化します。",
+ "apihelp-jsonfm-description": "データを JSON 形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-none-description": "何も出力しません。",
+ "apihelp-php-description": "データを PHP のシリアル化した形式で出力します。",
+ "apihelp-phpfm-description": "データを PHP のシリアル化した形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-rawfm-description": "データをデバッグ要素付きで JSON 形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-txt-description": "データを PHP の <code>print_r()</code> 形式で出力します。",
+ "apihelp-txtfm-description": "データを PHP の <code>print_r()</code> 形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-wddx-description": "データを WDDX 形式で出力します。",
+ "apihelp-wddxfm-description": "データを WDDX 形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-xml-description": "データを XML 形式で出力します。",
+ "apihelp-xml-param-xslt": "指定すると、スタイルシートとして &lt;xslt&gt; を追加します。MediaWiki 名前空間の、ページ名の末尾が \".xsl\" のウィキページに対して使用すべきです。",
+ "apihelp-xml-param-includexmlnamespace": "指定すると、XML 名前空間を追加します。",
+ "apihelp-xmlfm-description": "データを XML 形式 (HTML に埋め込んだ形式) で出力します。",
+ "apihelp-yaml-description": "データを YAML 形式で出力します。",
+ "apihelp-yamlfm-description": "データを YAML 形式 (HTML に埋め込んだ形式) で出力します。",
+ "api-format-title": "MediaWiki API の結果",
+ "api-format-prettyprint-header": "このページは $1 形式を HTML で表現したものです。HTML はデバッグに役立ちますが、アプリケーションでの使用には適していません。\n\n<var>format</var> パラメーターを指定すると出力形式を変更できます 。$1 形式の非 HTML 版を閲覧するには、format=$2 を設定してください。\n\n詳細情報については [[mw:API|完全な説明文書]]または [[Special:ApiHelp/main|API のヘルプ]]を参照してください。",
+ "api-help-title": "MediaWiki API ヘルプ",
+ "api-help-lead": "このページは自動生成された MediaWiki API の説明文書ページです。\n\n説明文書と例: https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "メイン モジュール",
+ "api-help-flag-deprecated": "このモジュールは廃止予定です。",
+ "api-help-flag-internal": "<strong>このモジュールは内部的または不安定です。</strong>動作が予告なく変更される場合があります。",
+ "api-help-flag-readrights": "このモジュールは読み取りの権限を必要とします。",
+ "api-help-flag-writerights": "このモジュールは書き込みの権限を必要とします。",
+ "api-help-flag-mustbeposted": "このモジュールは POST リクエストのみを受け付けます。",
+ "api-help-flag-generator": "このモジュールはジェネレーターとして使用できます。",
+ "api-help-parameters": "{{PLURAL:$1|パラメーター}}:",
+ "api-help-param-deprecated": "廃止予定です。",
+ "api-help-param-required": "このパラメーターは必須です。",
+ "api-help-param-list": "{{PLURAL:$1|1=値 (いずれか1つ)|2=値 (<kbd>{{!}}</kbd>で区切る)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=空欄にしてください|空欄にするか、または $2}}",
+ "api-help-param-integer-min": "{{PLURAL:$1|値}}は $2 以上にしてください。",
+ "api-help-param-integer-max": "{{PLURAL:$1|値}}は $3 以下にしてください。",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|値}}は $2 以上 $3 以下にしてください。",
+ "api-help-param-upload": "multipart/form-data 形式でファイルをアップロードしてください。",
+ "api-help-param-multi-separate": "複数の値は <kbd>|</kbd> で区切ってください。",
+ "api-help-param-multi-max": "値の最大値は {{PLURAL:$1|$1}} (ボットの場合は {{PLURAL:$2|$2}}) です。",
+ "api-help-param-default": "既定値: $1",
+ "api-help-param-default-empty": "既定値: <span class=\"apihelp-empty\">(空)</span>",
+ "api-help-param-token": "[[Special:ApiHelp/query+tokens|action=query&meta=tokens]] から取得した「$1」トークン",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(説明なし)</span>",
+ "api-help-examples": "{{PLURAL:$1|例}}:",
+ "api-help-permissions": "{{PLURAL:$1|権限}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|権限を持つグループ}}: $2",
+ "api-credits-header": "クレジット",
+ "api-credits": "API の開発者:\n* Roan Kattouw (2007年9月-2009年の主任開発者)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (作成者、2006年9月-2007年9月の主任開発者)\n* Brad Jorsch (2013年-現在の主任開発者)\n\nコメント、提案、質問は mediawiki-api@lists.wikimedia.org にお送りください。\nバグはこちらへご報告ください: https://phabricator.wikimedia.org/"
+}
diff --git a/includes/api/i18n/jam.json b/includes/api/i18n/jam.json
new file mode 100644
index 00000000..3c44fd2a
--- /dev/null
+++ b/includes/api/i18n/jam.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chabi1"
+ ]
+ },
+ "api-help-main-header": "Mien madyuul"
+}
diff --git a/includes/api/i18n/ko.json b/includes/api/i18n/ko.json
new file mode 100644
index 00000000..540f64c8
--- /dev/null
+++ b/includes/api/i18n/ko.json
@@ -0,0 +1,36 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kwj2772",
+ "Twotwo2019",
+ "아라"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [https://www.mediawiki.org/wiki/API:Main_page 설명문서]\n* [https://www.mediawiki.org/wiki/API:FAQ FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 메일링 리스트]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API 공지 사항] * [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 버그 및 요청] </div>\n<strong>상태:</strong> 이 페이지에 표시된 모든 기능은 정상 작동할 것이지만, API는 여전히 활발하게 개발되고 있으며, 언제든지 바뀔 수 있습니다. 업데이트 정보를 받아보려면 [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce 메일링 리스트]를 구독하십시오.\n\n<strong>잘못된 요청:</strong> API에 잘못된 요청이 전송되면 HTTP 헤더에서 \"MediaWiki-API-Error\" 키를 보내고, 헤더 값과 오류 코드가 같게 설정됩니다. 자세한 정보에 대해서는 https://www.mediawiki.org/wiki/API:Errors_and_warnings 를 참고하십시오.",
+ "apihelp-main-param-action": "수행할 동작",
+ "apihelp-main-param-format": "출력값의 형식.",
+ "apihelp-block-description": "사용자를 차단합니다.",
+ "apihelp-block-param-user": "차단하고자 하는 계정 이름, IP 주소 또는 대역",
+ "apihelp-block-param-expiry": "기한. 상대값(예시: \"5 months\" 또는 \"2 weeks\") 또는 절대값(예시: \"2014-09-18T12:34:56Z\")이 될 수 있습니다. \"infinite\", \"indefinite\" 또는 \"never\"로 설정하면 무기한으로 설정됩니다.",
+ "apihelp-block-param-reason": "차단 이유.",
+ "apihelp-block-param-anononly": "익명 사용자만 차단합니다. (즉, 이 IP의 익명 편집을 막음)",
+ "apihelp-block-param-nocreate": "계정 생성을 막습니다.",
+ "apihelp-block-param-autoblock": "최근 사용한 IP 주소나 로그인을 시도한 이후에 사용한 모든 IP 주소를 자동으로 차단합니다.",
+ "apihelp-block-param-noemail": "위키를 통해 이메일을 보내지 못하도록 막습니다. (<code>blockemail</code> 권한 필요)",
+ "apihelp-block-param-hidename": "차단 기록에서 사용자 이름을 숨깁니다. (<code>hideuser</code> 권한 필요)",
+ "apihelp-block-param-allowusertalk": "자신의 토론 문서를 편집할 수 있도록 허용합니다. (<var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var> 값에 따라 다름)",
+ "apihelp-block-param-reblock": "사용자가 이미 차단된 경우, 기존 차단 설정을 바꿉니다.",
+ "apihelp-block-param-watchuser": "해당 사용자 또는 IP 주소의 사용자 문서 및 토론 문서를 주시합니다.",
+ "apihelp-block-example-ip-simple": "IP <kbd>192.0.2.5</kbd>에 대해 <kbd>First strike</kbd>라는 이유로 3일간 차단하기",
+ "apihelp-block-example-user-complex": "사용자 <kbd>Vandal</kbd>을 <kbd>Vandalism</kbd>이라는 이유로 무기한 차단하며 계정 생성 및 이메일 발송을 막기",
+ "apihelp-delete-example-simple": "<kbd>Main Page</kbd>를 삭제합니다.",
+ "apihelp-edit-description": "문서를 만들고 편집합니다.",
+ "apihelp-edit-param-sectiontitle": "새 문단을 위한 제목.",
+ "apihelp-edit-param-text": "문서 내용.",
+ "apihelp-edit-param-summary": "편집 요약. 또한 $1section=new 및 $1sectiontitle이 설정되어 있지 않을 때 문단 제목.",
+ "apihelp-edit-param-minor": "사소한 편집.",
+ "apihelp-edit-param-notminor": "사소하지 않은 편집.",
+ "apihelp-edit-param-bot": "이 편집을 봇으로 표시.",
+ "api-help-param-list": "{{PLURAL:$1|1=하나의 값|2=값 (\"{{!}}\"로 구분)}}: $2",
+ "api-help-param-default": "기본값: $1"
+}
diff --git a/includes/api/i18n/ksh.json b/includes/api/i18n/ksh.json
new file mode 100644
index 00000000..cdcca375
--- /dev/null
+++ b/includes/api/i18n/ksh.json
@@ -0,0 +1,311 @@
+{
+ "@metadata": {
+ "authors": [
+ "Purodha"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page/de|Dokemäntazjohn]]\n* [[mw:API:FAQ/de|Öff jefrohch]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Mäileng_Leß]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Aanköndejonge zom <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i>]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Jemäldte Fähler un Wönsch]\n</div>\n<strong>Status:</strong> Alle op heh dä Sigg aanjzeischte Ußwahle sullte donn, ävver et <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i> wee jrahd noch äntwekeld un et kann sesch alle Nahslangs jädd ändere. Holl Der de [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ Mäileng_Leß med Aanköndejonge], öm automattesch övver Neujeschkeite enfommehrt ze wähde.\n\n<strong>Kapodde Aanfrohe:</strong> Wam_mer kapodde Aanfroheaan et API <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i> schek, kritt mer ene <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"HyperText Transfer Protocol\">HTTP</i>-Kopp ußjejovve met däm Täx „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">MediaWiki-API-Error</code>“ dren, dä mer als ene Schlößel bedraachte kann. Mih dohzoh fengk met op dä Sigg [[mw:API:Errors_and_warnings|<i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i>: Fähler un Warnonge]].",
+ "apihelp-main-param-action": "Wat för en Aufjahb.",
+ "apihelp-main-param-format": "Et Fommaht för ußzejävve.",
+ "apihelp-main-param-smaxage": "Säz <code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">s-maxage</code> em Kobb obb esu vill Sekonde. Fähler wähde nit faßjehallde.",
+ "apihelp-main-param-maxage": "Säz <code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">max-age</code> em Kobb obb esu vill Sekonde. Fähler wähde nit faßjehallde.",
+ "apihelp-main-param-assert": "Ställ sescher, dat dä Metmaacher enjelogg es (doh för jiff <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">user</kbd> en), udder ene Bot es (doh för jiff <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">bot</kbd> en).",
+ "apihelp-main-param-requestid": "Jehde Aanjahb vun heh weed widder med ußjejovve. Esuh kam_mer einzel Affrohre ussenein hallde.",
+ "apihelp-main-param-servedby": "Donn däm ẞööver, dä et jedonn hät, singe Nahme med ußjävve.",
+ "apihelp-main-param-curtimestamp": "Donn de aktoälle Zigg un et Dattum med ußjävve.",
+ "apihelp-block-description": "Ene Metmaacher schpärre.",
+ "apihelp-block-param-user": "Däm Nahme vun däm Metmaacher, de <i lang=\"en\" xml:lang=\"en\" title=\"Internet Protocol\">IP</i>-Addräß udder dä Berätt, dä De Schpärre wells.",
+ "apihelp-block-param-reason": "Der Schpärrjrond.",
+ "apihelp-block-param-anononly": "Bloß de nahmelohse Metmaaacher spärre, alsu donn et nahmelohse Beärbeide vun dä <i lang=\"en\" xml:lang=\"en\" title=\"Internet Protocol\">IP</i>-Addräß uß verhendere.",
+ "apihelp-block-param-nocreate": "Et Neu-Aanmelde verbeede",
+ "apihelp-block-param-autoblock": "Dun automattesch de läzde <i lang=\"en\" xml:lang=\"en\">IP</i>-Adräß schpärre, di dä Metmaacher jehatt hät, un och all di <i lang=\"en\" xml:lang=\"en\">IP</i>-Adräße, vun wo dä versöhk, jet ze ändere.",
+ "apihelp-block-param-reblock": "Wann dä Metmaacher als jeschpächd es, donn dat övverschrihve.",
+ "apihelp-block-param-watchuser": "Donn de Metmaachersigg un de Klaafsigg dohzoh op mig Oppaßleß säze.",
+ "apihelp-block-example-ip-simple": "Donn de <i lang=\"en\" xmL:lang=\"en\" title=\"Internet Protocol\">IP</i>-Addräß <kbd>192.0.2.5</kbd> för drei ääsch schpärre mem Jrond: <kbd>Eestschlaach</kbd>.",
+ "apihelp-compare-description": "Donn de Ongerscheide zwesche zwai Sigge beschtemme.\n\nDo moß derför jeweils en Väsjohn, en Övverschreff för di Sigg, odder ener Sigg iehr Kännong aanjävve, för de beide Sigge.",
+ "apihelp-compare-param-fromtitle": "Der Tettel vun dä eezte Sigg zom verjlihsche.",
+ "apihelp-compare-param-fromid": "De Kännong vun dä eezte Sigg zom verjlihsche.",
+ "apihelp-compare-param-fromrev": "De Väsjohn vun dä zwaite Sigg zom verjlihsche.",
+ "apihelp-compare-param-totitle": "Der Tettel vun dä zwaite Sigg zom verjlihsche.",
+ "apihelp-compare-param-toid": "De Kännong vun dä zwaite Sigg zom verjlihsche.",
+ "apihelp-compare-param-torev": "De Väsjohn vun dä zwaite Sigg zom verjlihsche.",
+ "apihelp-compare-example-1": "Fengk de Ongerscheide zwesche dä Väsjohne 1 un 2",
+ "apihelp-createaccount-description": "Ene neue Zohjang för ene Metmaacher aanlähje.",
+ "apihelp-createaccount-param-name": "Der Nahme för dä Metmaacher.",
+ "apihelp-createaccount-param-password": "Et Paßwoot (Weed ävver it jebruc un övverjange, wann <code lang=\"en\" xml:lang=\"en\"><var>$1mailpassword</var></code> jesaz es)",
+ "apihelp-createaccount-param-email": "Däm Metmaacher sing Adräß för de <i lang=\"en\" xml:lang=\"en\">e-mail</i>, kann och fott bliive.",
+ "apihelp-createaccount-param-realname": "Dämm Medmaacher singe reschtejje Nahme - kann fott blihve.",
+ "apihelp-createaccount-param-mailpassword": "Wann heh jädd aanjejovve es, kritt dä Metmaacher e zohfällesch ußjesöhk neu Paßwood aan sing Adräß för de <i lang=\"en\" xml:lang=\"en\">e-mail</i> jescheck.",
+ "apihelp-createaccount-param-reason": "Ene Jrond för dä Zojang aanzelähje, dä en de Logböhscher kütt.",
+ "apihelp-createaccount-param-language": "Dat Schprohcheköözel, wadd als der Schtandatt för dä Metmaacher jesaz wähde sull. Kann läddesch blihve, dann es et di Schprohch vum Wikki.",
+ "apihelp-createaccount-example-pass": "Lääsch dä Metmaacher <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">testuser</kbd> aan, mem Paßwood <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">test123</kbd>.",
+ "apihelp-createaccount-example-mail": "Lääsch dä Metmaacher <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">testmailuser</kbd> aan med emem zohfällesch ußjewörfelte Paßwoot un schegg_em dat övver de <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\">e-mail</i>.",
+ "apihelp-delete-description": "Schmieß en Sigg fott.",
+ "apihelp-delete-param-watch": "Donn di Sigg en däm aktoälle Metmaacher sing Oppaßleß opnämme.",
+ "apihelp-delete-param-unwatch": "Schmihß di Sigg us däm aktoälle Metmaacher singe Oppaßless erus.",
+ "apihelp-delete-example-simple": "Schmiiß de <kbd>Houpsigg</kbd> fott",
+ "apihelp-delete-example-reason": "Schmiiß de <kbd>Houpsigg</kbd> fott mem Jrond: <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Preparing for move</kbd>.",
+ "apihelp-disabled-description": "Dat Moduhl wohd affjeschalldt.",
+ "apihelp-edit-description": "Sigge aanlähje un verändere.",
+ "apihelp-edit-param-sectiontitle": "De Övverschreff för ene neue Affschnett.",
+ "apihelp-edit-param-text": "Dä Sigg ehre Ennhalld.",
+ "apihelp-edit-param-minor": "En klein Änderong.",
+ "apihelp-edit-param-notminor": "Kein klein Änderong.",
+ "apihelp-edit-param-bot": "Makeer heh di Änderog als vun enem Bot jemaat.",
+ "apihelp-edit-param-createonly": "Donn di Sigg nit ändere, wann se ald doh es.",
+ "apihelp-edit-param-nocreate": "Mäld ene Fähler, wann di Sigg nit doh es.",
+ "apihelp-edit-param-watch": "Donn di Sigg op dem aktälle Metmaacher sing Oppaßleß.",
+ "apihelp-edit-param-unwatch": "schmiiß di Sigg uß heh däm Metmaacher singe oppaßleß.",
+ "apihelp-edit-param-redirect": "Verfollsch de Ömleidonge automattesch.",
+ "apihelp-edit-param-contentmodel": "Et Enhalltsmodäll för dä neue Ennhalld.",
+ "apihelp-edit-example-edit": "Veränder en Sigg.",
+ "apihelp-edit-example-prepend": "Donn <kbd>_&#95;NOTOC_&#95;</kbd> för en Sigg säze.",
+ "apihelp-emailuser-description": "Donn en <i lang=\"en\" xml:lang=\"en\">e-mail</i> aan dä Metmaacher schecke.",
+ "apihelp-emailuser-param-target": "D ä Metmaacher, dä di <i lang=\"en\" xml:lang=\"en\">e-mail</i> krijje sull.",
+ "apihelp-emailuser-param-subject": "Koppeih mem Beträff.",
+ "apihelp-emailuser-param-text": "Dä Täx en dä <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\">e-mail</i>.",
+ "apihelp-emailuser-param-ccme": "scheck mer en Koppih vun heh dä <i lang=\"en\" xml:lang=\"en\">e-mail</i>.",
+ "apihelp-emailuser-example-email": "Donn en <i lang=\"en\" xml:lang=\"en\">e-mail</i> aan dä Metmaacher <kbd lang=\"en\" xml:lang=\"en\">WikiSysop</kbd> schecke mem Täx <kbd>Dä Enhalld</kbd> dren.",
+ "apihelp-expandtemplates-description": "Deiht alle Schablohne en Wikkitäx ömsäze.",
+ "apihelp-expandtemplates-param-title": "De Övverschreff vun dä Sigg.",
+ "apihelp-expandtemplates-param-text": "Dä Wikitäx zom ömwandelle.",
+ "apihelp-expandtemplates-param-includecomments": "Ov Aanmärkonge em <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"HyperText Markup Language\">HTML</i>-Fommaht med ußjejovve wähde sulle.",
+ "apihelp-feedcontributions-param-feedformat": "Däm Kannahl sing Fommaht.",
+ "apihelp-feedcontributions-param-year": "Vum johr un fröhjer.",
+ "apihelp-feedcontributions-param-month": "Vun däm Mohnd un derför",
+ "apihelp-feedcontributions-param-deletedonly": "zeijsch blohß de fottjeschmeße Beijdrähsch.",
+ "apihelp-feedcontributions-param-toponly": "Zeich blohß de Änderonge, di och de neußte sin.",
+ "apihelp-feedcontributions-param-newonly": "Zeich blohß de Änderonge, woh Sigge neu aanjelaat woode sin.",
+ "apihelp-feedcontributions-param-showsizediff": "Zeijsch de Ongerscheijd en de Jrühße zwesche de Väsjohne.",
+ "apihelp-feedcontributions-example-simple": "Zeijsch de Änderonge vum Metmaacher <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Example</kbd>.",
+ "apihelp-feedrecentchanges-param-feedformat": "Däm Kannahl sing Fommaht.",
+ "apihelp-feedrecentchanges-param-limit": "De hühßte Aanzahl vun Äjeebnesse för zeröck ze jävve",
+ "apihelp-feedrecentchanges-param-from": "Zeijsch de Änderonge zigg dämm.",
+ "apihelp-feedrecentchanges-param-hideminor": "De kein Minni_Änderonge verschteijsche.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Nohjelohrte Änderonge övverjonn.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Änderonge vun heh dämm Metmaacher övverjonn.",
+ "apihelp-feedwatchlist-param-feedformat": "Däm Kannahl sing Fommaht.",
+ "apihelp-filerevert-param-filename": "De Zih_Dattei, der ohne „{{ne:file}}“ derför.",
+ "apihelp-filerevert-param-comment": "Aanmärkong huh lahde.",
+ "apihelp-help-description": "zeisch Hölp för de aanjejovve Moduhle.",
+ "apihelp-help-example-recursive": "Alle Hölp en eine Sigg.",
+ "apihelp-help-example-help": "Alle Hölp övver de Hölp säälver.",
+ "apihelp-imagerotate-description": "Ein udder mieh Bellder driehje.",
+ "apihelp-imagerotate-param-rotation": "Öm wi vill Jrahd sulle de Bellder noh de Uhr drieh wääde?",
+ "apihelp-imagerotate-example-simple": "Drieh de <kbd>Dattei:Beijschpell.png</kbd> öm <kbd>90</kbd> Jrahd.",
+ "apihelp-imagerotate-example-generator": "Drieh alle Bellder en dä <kbd>Saachjropp:Ömdriehje</kbd> öm <kbd>180</kbd> Jrahd.",
+ "apihelp-import-param-xml": "Donn en Dattei em <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Extensible Markup Language\">XML</i>-Fommaht huhjahde.",
+ "apihelp-import-param-rootpage": "Als Ongersiff vun heh dä Sigg empottehre-",
+ "apihelp-login-param-name": "Metmaacher_Nahme.",
+ "apihelp-login-param-password": "Paßwoot.",
+ "apihelp-login-param-domain": "De Domaijn (kann fott bliehve)",
+ "apihelp-login-example-login": "Enlogge.",
+ "apihelp-logout-example-logout": "Donn dä aktoälle Metmaacher ußlogge.",
+ "apihelp-move-description": "Donn en Sigg ömbenänne",
+ "apihelp-move-param-to": "De neue Övverschreff för di Sigg drop ömzebenänne.",
+ "apihelp-move-param-reason": "Der jrond för di Sigg ömzebenänne.",
+ "apihelp-move-param-movetalk": "Donn de Klaafsigg ömbenänne, wann et se jitt.",
+ "apihelp-move-param-movesubpages": "Donn de Ongersigge ömbenänne, wann müjjelesch.",
+ "apihelp-move-param-noredirect": "Donn kein Ömleidong aanlähje.",
+ "apihelp-move-param-watch": "Donn de Sigg un de Ömleijdong op dem aktoälle Metmaacher sing Oppaßleß.",
+ "apihelp-move-param-unwatch": "Donn de Sigg un de Ömleijdong uß dem aktoälle Metmaacher sing Oppaßleß eruß nämme.",
+ "apihelp-move-param-watchlist": "Donn di Sigg en dem aktoälle Metmaacher sing Oppaßleß udder nemm se eruß, donn de Enschtällonge nämme udder donn de Oppaßleß nid ändere.",
+ "apihelp-move-param-ignorewarnings": "Donn alle Warnonge övverjonn",
+ "apihelp-move-example-move": "Donn <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Schlääschte Övverschreff</kbd> nach <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Johde Övverschreff</kbd> önmännde, der ohne en Ömleijdong aanzelähje.",
+ "apihelp-opensearch-description": "Em Wikki söhke mem <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"„Offe Söhke“\">OpenSearch</i>",
+ "apihelp-opensearch-param-search": "Noh wat söhke?",
+ "apihelp-opensearch-param-limit": "De hühßte Aanzahl vun Äjeebnesse för zeröck ze jävve",
+ "apihelp-opensearch-param-namespace": "En wällschem Appachtemang söhke.",
+ "apihelp-opensearch-param-redirects": "How to handle redirects:\n;return:Return the redirect itself.\n;resolve:Return the target page. May return fewer than $1limit results.\nFor historical reasons, the default is \"return\" for $1format=json and \"resolve\" for other formats.\n<!-- \nhttps://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Apihelp-opensearch-param-redirects/en\n-->",
+ "apihelp-opensearch-param-format": "Et Fommaht zom Ußjävve.",
+ "apihelp-opensearch-example-te": "Fengk Sigge, di met <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Te</kbd> aanfange.",
+ "apihelp-options-param-reset": "Säz de Enschtällonge op dem Wikki singe Standatt.",
+ "apihelp-options-example-reset": "Alle enschtälloonge retuur schtälle.",
+ "apihelp-paraminfo-description": "Holl Aanjahbe övver dä <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i> ier Moduhle.",
+ "apihelp-paraminfo-param-helpformat": "Et Fommaht vun de Täxe för Hölp.",
+ "apihelp-paraminfo-param-formatmodules": "Leß met de Nahme vun de Moduhle zom Fommatehre (Wäät vum „<var lang=\"en\" xml:lang=\"en\" dir=\"ltr\">format</var>“-Parramehter). Nemm schtatt dämm „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$1modules</kbd>“.",
+ "apihelp-paraminfo-example-1": "Zisch Aanjahbe övver <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, un <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+ "apihelp-parse-param-summary": "De Zersammefaßong för ze pahse.",
+ "apihelp-parse-param-section": "Holl blohß dann der Ennhalld vun däm Affschnett met dä Nommer, udder wann „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">new</kbd>“ enjejovve es, maach ene neu Affschnett derbei.",
+ "apihelp-parse-param-sectiontitle": "De Övverschreff för dä neuje Afschnet, wann <var lang=\"en\" xml:lang=\"en\" dir=\"ltr\">section</var> = <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">new</kbd> es.\n\nAnders wi beim Beärbeide vun dä Sigg weed dä Parramehter nit dorsch de <var lang=\"en\" xml:lang=\"en\" dir=\"ltr\">summary</var> ußjetuusch, wann hä fottjelohße udder läddesch es.",
+ "apihelp-parse-example-page": "Donn en Sigg pahse.",
+ "apihelp-parse-example-text": "Donn Wikkitäx pahse.",
+ "apihelp-parse-example-texttitle": "Donn Wikkitäx pahse, un jiff derför en Övverschreff för en Sigg aan.",
+ "apihelp-parse-example-summary": "Donn Zersammefaßong pahse.",
+ "apihelp-patrol-description": "Donn en Sigg udder Väsjohn nohkike.",
+ "apihelp-patrol-param-rcid": "De Kännong in de läzde Änderonge zum Nohkike.",
+ "apihelp-patrol-param-revid": "De Kännong vun dä Väsjohn zum Nohkike.",
+ "apihelp-patrol-example-rcid": "Donn en läzde Änderonge nohkike.",
+ "apihelp-patrol-example-revid": "Donn en Väsjohn nohkike.",
+ "apihelp-protect-description": "Änder der Siggeschoz för en Sigg.",
+ "apihelp-protect-param-title": "De Övverschreff vun dä Sigg zom Schöze udder Freijävve. Kam_mer nit zesamme met\n„<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$1pageid</code>“ bruche.",
+ "apihelp-protect-param-pageid": "De Kännong vun dä Sigg zom Schöze udder Freijävve. Kam_mer nit zesamme met\n„<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$1pageid</code>“ bruche.",
+ "apihelp-protect-param-reason": "Der Jrond för et Schöze udder Freijävve.",
+ "apihelp-protect-example-protect": "Donn en Sigg schöze.",
+ "apihelp-query-param-list": "Wat för en Leßte holle.",
+ "apihelp-query-param-meta": "Wat för en Matta_Dahte ze holle.",
+ "apihelp-query+allcategories-description": "Alle Saachjroppe opzälle.",
+ "apihelp-query+allcategories-param-from": "De Saachjropp, vun woh aan opzälle.",
+ "apihelp-query+allcategories-param-to": "De Saachjropp, bes woh hen opzälle.",
+ "apihelp-query+allcategories-param-dir": "De Reijefollsch zum Zotehre.",
+ "apihelp-query+allcategories-param-limit": "Wi vell Saachjroppe ußjävve?",
+ "apihelp-query+allcategories-param-prop": "Which properties to get:\n;size:Adds number of pages in the category.\n;hidden:Tags categories that are hidden with _&#95;HIDDENCAT_&#95;.\n<!-- \nhttps://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Apihelp-query%2Ballcategories-param-prop/ksh\n-->",
+ "apihelp-query+alldeletedrevisions-description": "Donn alle fottjeschmeße Väsjohne vun enem Metmaacher udder en enem Appachemang opleßte.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Kam_mer blohß met <var lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$3user</var> bruche.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Kam_mer nit met <var lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$3user</var> bruche.",
+ "apihelp-query+alldeletedrevisions-param-start": "Et Dattom un de Zigg vun woh aff opjezallt wähde sull.",
+ "apihelp-query+alldeletedrevisions-param-end": "Et Dattom un de Zigg bes woh hen opjezallt wähde sull.",
+ "apihelp-query+alldeletedrevisions-param-from": "Bejenn de Leß bei heh dä Överschreff.",
+ "apihelp-query+alldeletedrevisions-param-to": "Hühr de Leß bei heh dä Överschreff oop.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Söhk noh Sigge, woh de Övverschrevv esu aanfängk.",
+ "apihelp-query+alldeletedrevisions-param-user": "Donn blohß Väsjohne vun heh däm Metmaacher opleßte.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Donn kein Väsjohne vun heh däm Metmaacher opleßte.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Donn blohß Siegg en heh däm Appachtemang opleßte.",
+ "apihelp-query+alldeletedrevisions-param-generatetitles": "Wann als ene Jenerahtor enjesaz, bräng Övverschreffte un kein Kännonge vu Väsjohne.",
+ "apihelp-query+alldeletedrevisions-example-user": "Donn de läzde fuffzisch fottjeschmeße Beijdrähsch vim Metmaacher „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Example<kbd>“ opleste.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Donn de läzde fuffzisch fottjeschmeße Väsjohne em Houp-Appachemang opleste.",
+ "apihelp-query+allfileusages-description": "Donn alle Dattei_Oprohfe opleste, och vun Datteije, di (noch) nit doh sin.",
+ "apihelp-query+allfileusages-param-from": "De Övverschreff vun dä Dattei, woh de Leß medd aanfange sull.",
+ "apihelp-query+allfileusages-param-to": "De Övverschreff vun dä Dattei, woh de Leß medd ophühre sull.",
+ "apihelp-query+allfileusages-param-prefix": "Söhk noh alle Övverschreffte, di met heh däm Täx aanfange.",
+ "apihelp-query+allfileusages-param-unique": "Donn blohß ongerscheidlijje Övverschreffte vun Datteije aanzeije. Kammer nit zesamme met „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$1prop=ids<code>“ bruche.\nWann als ene Jenerahtor enjesaz, bräng Zihlsigge un kein Kwällsigge.",
+ "apihelp-query+allfileusages-param-prop": "Which pieces of information to include:\n;ids:Adds the page ID of the using page (cannot be used with $1unique).\n;title:Adds the title of the file.\n<!-- \nhttps://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Apihelp-query%2Ballfileusages-param-prop/ksh\n-->",
+ "apihelp-query+allfileusages-param-limit": "Wi vill sulle överhoup aanjezeisch wähde?",
+ "apihelp-query+allfileusages-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+allfileusages-example-B": "Donn Övverschreffte vun Datteije aanzeije, och vun Datteije, di (noch) nit doh sin, zesame met dä Kännonge vun dä Sigge, woh se vun sin, aanjevange vun <kbd>B</kbd>.",
+ "apihelp-query+allfileusages-example-unique": "Donn ongerscheidlejje Övverschreffte vun Datteije opleßte.",
+ "apihelp-query+allfileusages-example-generator": "Holl Sigge, woh Datteieje dren vorkumme.",
+ "apihelp-query+allimages-description": "Donn alle Bellder der Reih noh opzälle.",
+ "apihelp-query+allimages-param-sort": "De Eijeschavv öm dernoh ze zottehre.",
+ "apihelp-query+allimages-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+allimages-param-limit": "Wi vell Bellder ennsjesamp ußjävve.",
+ "apihelp-query+allimages-example-generator": "Zeisch Aanjahbe övver veer Bellder un bejenn mem Bohchschtabe <kbd>T</kbd>.",
+ "apihelp-query+alllinks-description": "Donn alle Lengk opzälle, di en e beschtemmpt Appachtemang jonn.",
+ "apihelp-query+alllinks-param-from": "De Övverschreff vun däm Lengk, woh de Leß medd aanfange sull.",
+ "apihelp-query+alllinks-param-to": "De Övverschreff vun dä Dattei, woh et Zälle ophühre sull.",
+ "apihelp-query+alllinks-param-prefix": "Söhk noh alle verlengk Övverschreffte, di met heh däm Täx aanfange.",
+ "apihelp-query+alllinks-param-namespace": "Dat Appachtemang zom opzälle.",
+ "apihelp-query+alllinks-param-limit": "Wi vill sulle överhoup aanjezeisch wähde?",
+ "apihelp-query+alllinks-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+alllinks-example-unique": "Leß ongerscheidlejje verlengk Övverschreffte.",
+ "apihelp-query+alllinks-example-generator": "Holl Sigge, di di Lengks änthallde.",
+ "apihelp-query+allmessages-description": "Donn em Wikki sing Täxte un Nohreescht ußjävve.",
+ "apihelp-query+allmessages-param-prop": "Wat för en Eijeschaffte holle.",
+ "apihelp-query+allmessages-param-args": "De Parramehtere för en dä Täx udder en di Nohreesch enzeföhje.",
+ "apihelp-query+allmessages-param-filter": "Jiv blohß de Täxte un Nohreesche uß, woh heh dat Täxschtöck dren änthallde es.",
+ "apihelp-query+allmessages-param-customised": "Jiv bloß de Täxte un Nohreesche en heh däm Zohschtand uß.",
+ "apihelp-query+allmessages-param-lang": "Jiv de Täxte un Nohreesche en heh dä Schprohch uß.",
+ "apihelp-query+allmessages-param-from": "Jiv de Täxte un Nohreesche vun heh aan uß.",
+ "apihelp-query+allmessages-param-to": "Jiv de Täxte un Nohreesche bes heh uß.",
+ "apihelp-query+allmessages-param-prefix": "Jiv de Täxte un Nohreesche met heh däm Aanfang uß.",
+ "apihelp-query+allmessages-example-ipb": "Zeijsch Täxde udder Nohreeschte di met „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">ipb</kbd>“ aanfange.",
+ "apihelp-query+allmessages-example-de": "Zeijsch de Täxde udder Nohreeschte „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">august</kbd>“ un „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">mainpage</kbd>“ en Deutsch aan.",
+ "apihelp-query+allpages-description": "Donn alle Sigge der Reih noh en enem jejovve Appachtemang aan.",
+ "apihelp-query+allpages-param-from": "De Övverschreff vun dä Sigg, woh de Leß medd aanfange sull.",
+ "apihelp-query+allpages-param-to": "De Kännong vun dä Sigg, bes woh hen opzälle.",
+ "apihelp-query+allpages-param-prefix": "Söhk noh alle Övverschreffte vun Sigge, di met heh dämm Wäät bejenne.",
+ "apihelp-query+allpages-param-namespace": "De Saachjropp zom opzälle.",
+ "apihelp-query+allpages-param-filterredir": "Wat för en Sigg zem opleßte.",
+ "apihelp-query+allpages-param-minsize": "Bejränz op Sigge met winneschßdens esu vill Bytes dren.",
+ "apihelp-query+allpages-param-maxsize": "Bejränz op Sigge met hüüschßdens esu vill Bytes dren.",
+ "apihelp-query+allpages-param-prtype": "Bejränz op jeschöz Sigge.",
+ "apihelp-query+allpages-param-limit": "Wi vill Sigge zen aanzeije?",
+ "apihelp-query+allpages-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+allpages-example-B": "Zeisch en Leß met Sigge un bejenn mem Bohchschtabe <kbd>B</kbd>.",
+ "apihelp-query+allpages-example-generator": "Zeisch Aanjahbe övver veer Bellder un bejenn mem Bohchschtabe <kbd>T</kbd>.",
+ "apihelp-query+allpages-example-generator-revisions": "Zeisch der Enhalld vu de eetsde zwai Sigg un bejenn bei <kbd>Re</kbd>.",
+ "apihelp-query+allredirects-description": "Alle Ömleidonge op e beschtemmp Appachtemang opleßte.",
+ "apihelp-query+allredirects-param-from": "De Övverschreff vun dä Ömleidong, woh de Leß medd ophühre sull.",
+ "apihelp-query+allredirects-param-to": "De Övverschreff vun dä Sigg, woh et Zälle ophühre sull.",
+ "apihelp-query+allredirects-param-prefix": "Söhk not sigge, die esu aanfange.",
+ "apihelp-query+allredirects-param-namespace": "Dat Appachtemang zom opzälle.",
+ "apihelp-query+allredirects-param-limit": "Wi vill sulle överhoup aanjezeisch wääde?",
+ "apihelp-query+allredirects-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+allredirects-example-B": "Zeisch Zihlsigge, och di et (noch) nit jitt, met dä Kännonge, wo se her sin, un bejenn mem Bohchschtabe <kbd>B</kbd>.",
+ "apihelp-query+allredirects-example-unique": "Ongerscheidlijje Sigge opleste.",
+ "apihelp-query+allredirects-example-generator": "Holl de Sigge met de Ömleidonge.",
+ "apihelp-query+alltransclusions-param-namespace": "Dat Appachtemang zom opzälle.",
+ "apihelp-query+alltransclusions-param-limit": "Wi vill sulle överhoup aanjezeisch wähde?",
+ "apihelp-query+alltransclusions-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+allusers-description": "Donn alle aanjemälldte Metmaacher opzälle.",
+ "apihelp-query+allusers-param-from": "Dä Metmaacher_Nahme vun woh aan opzälle.",
+ "apihelp-query+allusers-param-to": "Dä Metmaacher_Nahme bes woh hen opzälle.",
+ "apihelp-query+allusers-param-prefix": "Söhk noh alle Metmaacher_Nahme, di mit heh däm Wäät bejenne.",
+ "apihelp-query+allusers-param-dir": "De Reijefollsch zum Zotehre.",
+ "apihelp-query+allusers-param-group": "Donn blohß Metmaacher uß dä aanjejovve Jroppe enschlehße.",
+ "apihelp-query+allusers-param-excludegroup": "Donn keine Metmaacher uß dä aanjejovve Jroppe enschlehße.",
+ "apihelp-query+allusers-param-limit": "Wi vill Nahme Metmaacher sulle mer krijje?",
+ "apihelp-query+allusers-param-witheditsonly": "Blohß Metmahcher, di och ens jät verändert han.",
+ "apihelp-query+allusers-param-activeusers": "Donn blohß Metmaacher opleßte, di {{PLURAL:$1|der läzde Daach|en de läzde $1 Dääsch|keine läzde Daach}} aktihf wohre.",
+ "apihelp-query+allusers-example-Y": "Monn metmaacher opleßte, woh de Nahme vun met <kbd>Y</kbd> aanfange.",
+ "apihelp-query+backlinks-description": "Fengk alle Sigge, di op de aanjejovve Sigg lengke.",
+ "apihelp-query+backlinks-param-namespace": "Dat Appachtemang zom opzälle.",
+ "apihelp-query+backlinks-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+backlinks-example-simple": "Zeijsch Lengks op de Sigg <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Main page<kbd>.",
+ "apihelp-query+blocks-param-start": "Et Dattom un de Zigg vun woh aff opjezallt wähde sull.",
+ "apihelp-query+blocks-param-end": "Et Dattom un de Zigg bes woh hen opjezallt wähde sull.",
+ "apihelp-query+blocks-param-limit": "De hühßde Aanzahl Spärre zom opleste.",
+ "apihelp-query+blocks-param-prop": "Which properties to get:\n;id:Adds the ID of the block.\n;user:Adds the username of the blocked user.\n;userid:Adds the user ID of the blocked user.\n;by:Adds the username of the blocking user.\n;byid:Adds the user ID of the blocking user.\n;timestamp:Adds the timestamp of when the block was given.\n;expiry:Adds the timestamp of when the block expires.\n;reason:Adds the reason given for the block.\n;range:Adds the range of IP addresses affected by the block.\n;flags:Tags the ban with (autoblock, anononly, etc.).\n<!-- \nhttps://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Apihelp-query%2Bblocks-param-prop/ksh\n-->",
+ "apihelp-query+blocks-example-simple": "Schpärre opleßte.",
+ "apihelp-query+blocks-example-users": "Donn de Schpärre vun dä Metmaacher <lang=\"en\" xml:lang=\"en\" dir=\"ltr\" kbd>Alice</kbd> un <lang=\"en\" xml:lang=\"en\" dir=\"ltr\" kbd>Bob</kbd> opleßte.",
+ "apihelp-query+categories-description": "Donn alle Saachjroppe epleßte, woh di Sigge dren sin.",
+ "apihelp-query+categories-param-prop": "Which additional properties to get for each category:\n;sortkey:Adds the sortkey (hexadecimal string) and sortkey prefix (human-readable part) for the category.\n;timestamp:Adds timestamp of when the category was added.\n;hidden:Tags categories that are hidden with _&#95;HIDDENCAT_&#95;.\n<!-- \nhttps://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Apihelp-query%2Bcategories-param-prop/ksh\n-->",
+ "apihelp-query+categories-param-show": "Wat för en Zoot Saachjroppe zeije.",
+ "apihelp-query+categories-param-limit": "Wi vell Saachjroppe ußjävve?",
+ "apihelp-query+categories-param-dir": "En wälsche Reijefollsch?",
+ "apihelp-query+categories-example-simple": "Holl en Leß med alle Saachjroppe, woh di Sigg <kbd>Albert Einstein</kbd> dren es.",
+ "apihelp-query+categories-example-generator": "Holl Aanjahbe övver alle Saachjroppe, di en dä Sigg <kbd>Albert Einstein</kbd> jebruch wähde.",
+ "apihelp-query+categoryinfo-description": "Holl Aanjahbe övver de aanjejovve Saachjroppe.",
+ "apihelp-query+categorymembers-description": "Donn alle Sigge en ener aanjejove saachjrobb opleste.",
+ "apihelp-query+categorymembers-param-limit": "De jrüüßte Zahl Sigge för ußzejävve.",
+ "apihelp-query+categorymembers-param-sort": "De Eijeschavv öm dernoh ze zottehre.",
+ "apihelp-query+categorymembers-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+categorymembers-param-startsortkey": "Söhk „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$1starthexsortkey</code>“ schtatt dämm.",
+ "apihelp-query+categorymembers-param-endsortkey": "Söhk „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$1endhexsortkey </code>“ schtatt dämm.",
+ "apihelp-query+categorymembers-example-simple": "Holl de eezde zehn Sigge de dä <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Category:Physics</kbd>.",
+ "apihelp-query+categorymembers-example-generator": "Holl anjahbe övver de eezde zehn Sigge de dä <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Category:Physics</kbd>.",
+ "apihelp-query+deletedrevisions-param-user": "Donn blohß Väsjohne vun heh däm Metmaacher opleßte.",
+ "apihelp-query+deletedrevisions-param-excludeuser": "Donn kein Väsjohne vun heh däm Metmaacher opleßte.",
+ "apihelp-query+deletedrevisions-param-limit": "De hühßde Aanzahl Väsjohne för opzeleßte.",
+ "apihelp-query+deletedrevisions-param-prop": "Wat för en Eijeschaffte holle:\n;revid:Adds the revision ID of the deleted revision.\n;parentid:Adds the revision ID of the previous revision to the page.\n;user:Adds the user who made the revision.\n;userid:Adds the user ID who made the revision.\n;comment:Adds the comment of the revision.\n;parsedcomment:Adds the parsed comment of the revision.\n;minor:Tags if the revision is minor.\n;len:Adds the length (bytes) of the revision.\n;sha1:Adds the SHA-1 (base 16) of the revision.\n;content:Adds the content of the revision.\n;tags:Tags for the revision.\n<!-- \nhttps://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Apihelp-query%2Bdeletedrevisions-param-prop/en\n-->",
+ "apihelp-query+deletedrevisions-example-revids": "Donn de Aanjahbe för de fottjeschmeße Väsjohn <kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">123456</kbd> holle.",
+ "apihelp-query+duplicatefiles-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+embeddedin-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+filearchive-param-dir": "En wälsche Reijefollsch opleßte.",
+ "apihelp-query+images-param-dir": "En wälsche Reijefollsch opleßte.",
+ "apihelp-query+imageusage-param-namespace": "Dat Appachtemang zom opzälle.",
+ "apihelp-query+imageusage-param-dir": "En wälsche Reijefollsch opleßte.",
+ "apihelp-query+info-paramvalue-prop-watchers": "De Aanzahl Oppaßer, wann zohjelohße.",
+ "apihelp-query+iwbacklinks-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+iwlinks-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+langbacklinks-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+langlinks-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+links-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+pagepropnames-param-limit": "De jrüüßte Zahl Nahme för ußzejävve.",
+ "apihelp-query+pagepropnames-example-simple": "Holl de eezde zehn Nahme vun Eijeschaffte.",
+ "apihelp-query+pageswithprop-param-limit": "De jrüüßte Zahl Sigge för ußzejävve.",
+ "apihelp-query+pageswithprop-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+prefixsearch-param-namespace": "En wällschem Appachtemang söhke.",
+ "apihelp-query+prefixsearch-param-limit": "De hühßte Aanzahl vun Äjeebnesse för zeröck ze jävve",
+ "apihelp-query+querypage-param-limit": "De Aanzahl vun Äjeebnesse för zeröck ze jävve",
+ "apihelp-query+random-param-limit": "Wi vill zohfälleje Sigge sulle ußjejovve wähde?",
+ "apihelp-query+revisions+base-param-limit": "Wi vill Väsjohne sulle ußjejovve wähde?",
+ "apihelp-query+siteinfo-param-numberingroup": "Donn de Aanzahl Metmaacher en de Jroppe vun Metmaacher opleßte.",
+ "apihelp-query+tags-param-limit": "De hühßde Aanzahl !!FUZY tags zom opleste.",
+ "apihelp-query+tags-param-prop": "Wat för en Eijschaffte holle:\n;name:Adds name of tag.\n;displayname:Adds system message for the tag.\n;description:Adds description of the tag.\n;hitcount:Adds the number of revisions and log entries that have this tag.\n;defined:Indicate whether the tag is defined.\n;source:Gets the sources of the tag, which may include <samp>extension</samp> for extension-defined tags and <samp>manual</samp> for tags that may be applied manually by users.\n;active:Whether the tag is still being applied.\n<!-- https://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Apihelp-query%2Btags-param-prop/ksh\n -->",
+ "apihelp-query+templates-param-limit": "Wi vill Schablohne sulle ußjejovve wähde?",
+ "apihelp-query+templates-param-dir": "En wälsche Reihjefollsch opleßte.",
+ "apihelp-query+usercontribs-param-limit": "De hühßte Aanzahl vun Meddeilonge för zeröck ze jävve",
+ "api-help-param-default": "Schtandatt: $1",
+ "api-help-param-default-empty": "Schtandatt: <span class=\"apihelp-empty\">(läddesch)</span>",
+ "api-help-param-limited-in-miser-mode": "<strong>opjepaß:</strong> Weil der [[mw:Manual:$wgMiserMode|miser mode]] enjeschalld es, künne heh winnijer wi <var lang=\"en\" xml:lang=\"en\" dir=\"ltr\">$1limit</var> Äjehpneße ußjejejovve wähde, vör em Wigger_Mache. En Jränzfäll künne et Noll sin.",
+ "api-help-param-direction": "En wälsche Reihjefollsch opleßte:\n;newer:List oldest first. Note: $1start has to be before $1end.\n;older:List newest first (default). Note: $1start has to be later than $1end.\n<!-- \nhttps://translatewiki.net/wiki/Thread:Support/About_MediaWiki:Api-help-param-direction/ksh\n-->",
+ "api-help-param-continue": "Wann mih ze holle es, nemm dat för wigger ze maache.",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(nix drövver bikannt)</span>",
+ "api-help-examples": "{{PLURAL:$1|Beijschpell|Beijschpelle|Beijschpell}}:",
+ "api-help-permissions": "{{PLURAL:$1|Rääsch|Rääschde|Rääsch}}:",
+ "api-help-permissions-granted-to": "Jejovve aan: $2{{PLURAL:$1|}}",
+ "api-help-right-apihighlimits": "Donn de Beschängkonge vun Opdrähscht aan de <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i> kleiner maache (langsamme Opdrähscht: $1; flöcke Opdrähscht: $2). De Beschränkonge för lahme Opdrähscht jällde och för Parramehtere met vill Wähte.",
+ "api-credits-header": "Aanäkännong för Beijdrähsch",
+ "api-credits": "Dä <i lang=\"en\" xml:lang=\"en\" dir=\"ltr\" title=\"Application Programming Interface\">API</i> ier Äntweklere:\n* Roan Kattouw (Aanföhrer zigg em Säptämber 2007 bes 2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (Bejenner un Aanföhrer vum Säptämber 2006 bes Säptämber 2007)\n* Brad Jorsch (Aanföhrer vun 2013 bes hük)\n\nDoht Ühr Aanmärkonge, Vörschlähsch un Frohre aan de Meijlengleß <code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">mediawiki-api@lists.wikimedia.org</code> scheke, Ühr Vörschlähsch un Fählermälldong doht op <code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">https://phabricator.wikimedia.org/</code> ennjävve."
+}
diff --git a/includes/api/i18n/ku-latn.json b/includes/api/i18n/ku-latn.json
new file mode 100644
index 00000000..a40e4c11
--- /dev/null
+++ b/includes/api/i18n/ku-latn.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "George Animal"
+ ]
+ },
+ "apihelp-delete-description": "Rûpelekê jê bibe.",
+ "apihelp-expandtemplates-param-title": "Sernavê rûpelê."
+}
diff --git a/includes/api/i18n/lb.json b/includes/api/i18n/lb.json
new file mode 100644
index 00000000..ac2a1d34
--- /dev/null
+++ b/includes/api/i18n/lb.json
@@ -0,0 +1,94 @@
+{
+ "@metadata": {
+ "authors": [
+ "Robby"
+ ]
+ },
+ "apihelp-block-description": "E Benotzer spären.",
+ "apihelp-block-param-user": "Benotzernumm, IP-Adress oder IP-Beräich deen Dir späre wëllt.",
+ "apihelp-block-param-reason": "Grond fir ze spären.",
+ "apihelp-block-param-anononly": "Nëmmen anonym Benotzer spären (z. Bsp. anonym Ännerunge vun dëser IP-Adress ausschalten)",
+ "apihelp-block-param-nocreate": "Opmaache vun engem Benotzerkont verhënneren.",
+ "apihelp-block-param-reblock": "Wann de Benotzer scho gespaart ass, déi aktuell Spär iwwerschreiwen.",
+ "apihelp-block-param-watchuser": "Dem Benotzer oder der IP-Adress hier Benotzer- an Diskussiouns-Säiten iwwerwaachen.",
+ "apihelp-compare-param-fromtitle": "Éischten Titel fir ze vergläichen.",
+ "apihelp-createaccount-description": "En neie Benotzerkont uleeën.",
+ "apihelp-createaccount-param-name": "Benotzernumm.",
+ "apihelp-createaccount-param-email": "E-Mail-Adress vum Benotzer (fakultativ).",
+ "apihelp-createaccount-param-realname": "Richtegen Numm vum Benotzer (fakultativ).",
+ "apihelp-delete-description": "Eng Säit läschen.",
+ "apihelp-delete-param-watch": "D'Säit op dem aktuelle Benotzer seng Iwwerwaachungslëscht dobäisetzen.",
+ "apihelp-delete-example-simple": "D'<kbd>Haaptsäit</kbd> läschen.",
+ "apihelp-disabled-description": "Dëse Modul gouf ausgeschalt.",
+ "apihelp-edit-param-sectiontitle": "Den Titel fir en neien Abschnitt.",
+ "apihelp-edit-param-minor": "Kleng Ännerung.",
+ "apihelp-edit-param-bot": "Dës Ännerung als Bot-Ännerung markéieren.",
+ "apihelp-edit-param-watch": "D'Säit op dem aktuelle Benotzer seng Iwwerwaachungslëscht dobäisetzen.",
+ "apihelp-edit-example-edit": "Eng Säit änneren",
+ "apihelp-expandtemplates-param-title": "Titel vun der Säit.",
+ "apihelp-feedrecentchanges-param-hideminor": "Kleng Ännerunge verstoppen.",
+ "apihelp-feedrecentchanges-param-hideanons": "Ännerunge vun anonyme Benotzer verstoppen.",
+ "apihelp-feedrecentchanges-param-hideliu": "Ännerunge vu registréierte Benotzer verstoppen.",
+ "apihelp-feedrecentchanges-example-simple": "Rezent Ännerunge weisen",
+ "apihelp-help-example-main": "Hëllef fir den Haaptmodul.",
+ "apihelp-help-example-recursive": "All Hëllef op enger Säit",
+ "apihelp-imagerotate-description": "Eent oder méi Biller dréinen.",
+ "apihelp-imagerotate-example-generator": "All Biller an der <kbd>Category:Flip]]<kbd> ëm <kbd>180<kbd> Grad dréinen.",
+ "apihelp-import-param-summary": "Resumé importéieren.",
+ "apihelp-login-param-name": "Benotzernumm.",
+ "apihelp-login-param-password": "Passwuert.",
+ "apihelp-login-example-login": "Aloggen.",
+ "apihelp-move-description": "Eng Säit réckelen.",
+ "apihelp-move-param-ignorewarnings": "All Warnungen ignoréieren.",
+ "apihelp-options-example-reset": "All Astellungen zrécksetzen",
+ "apihelp-patrol-example-rcid": "Eng rezent Ännerung nokucken.",
+ "apihelp-patrol-example-revid": "Eng Versioun nokucken.",
+ "apihelp-protect-example-protect": "Eng Säit spären",
+ "apihelp-query+allcategories-description": "All Kategorien opzielen.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Kann nëmme mam <var>$3user</var> benotzt ginn.",
+ "apihelp-query+alldeletedrevisions-param-user": "Nëmme Versioune vun dësem Benotzer opzielen.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Versioune vun dësem Benotzer net opzielen.",
+ "apihelp-query+allusers-description": "All registréiert Benotzer opzielen.",
+ "apihelp-query+allusers-param-activeusers": "Nëmme Benotzer opzielen déi an de leschten $1 {{PLURAL:$1|Dag|Deeg}} aktiv waren.",
+ "apihelp-query+blocks-description": "Lëscht vun de gespaarte Benotzer an IP-Adressen.",
+ "apihelp-query+blocks-example-simple": "Lëscht vun de Spären",
+ "apihelp-query+categories-description": "All Kategorien opzielen zu deenen dës Säit gehéiert.",
+ "apihelp-query+categorymembers-description": "All Säiten aus enger bestëmmter Kategorie opzielen.",
+ "apihelp-query+categorymembers-example-simple": "Déi éischt 10 Säiten aus der <kbd>Category:Physics</kbd> kréien.",
+ "apihelp-query+deletedrevisions-param-excludeuser": "Versioune vun dësem Benotzer net opzielen.",
+ "apihelp-query+deletedrevs-param-unique": "Nëmmen eng Versioun fir all Säit weisen.",
+ "apihelp-query+filearchive-example-simple": "Eng Lëscht vun alle geläschte Fichiere weisen",
+ "apihelp-query+imageinfo-paramvalue-prop-user": "Setzt fir all Versioun vum Fichier de Benotzer dobäi deen en eropgelueden huet.",
+ "apihelp-query+imageinfo-paramvalue-prop-comment": "Bemierkung iwwert d'Versioun.",
+ "apihelp-query+imageinfo-paramvalue-prop-dimensions": "Alias fir Gréisst.",
+ "apihelp-query+imageinfo-param-urlheight": "Ähnlech wéi $1urlwidth.",
+ "apihelp-query+images-example-simple": "Eng Lëscht vun de Fichiere kréien déi op der [[Main Page|Haaptsäit]] benotzt ginn",
+ "apihelp-query+imageusage-example-simple": "Säite weisen déi [[:File:Albert Einstein Head.jpg]] benotzen",
+ "apihelp-query+info-paramvalue-prop-readable": "Ob de Benotzer dës Säit liese kann.",
+ "apihelp-query+langlinks-param-lang": "Nëmme Sproochlinke mat dësem Sproochcode zréckginn.",
+ "apihelp-query+protectedtitles-param-namespace": "Nëmmen Titelen aus dësen Nummraim opzielen.",
+ "apihelp-query+recentchanges-param-user": "Nëmmen Ännerunge vun dësem Benotzer opzielen.",
+ "apihelp-query+recentchanges-example-simple": "Rezent Ännerunge weisen",
+ "apihelp-query+revisions-example-last5": "Déi lescht 5 Versioune vun der <kbd>Haaptsäit</kbd> kréien.",
+ "apihelp-query+usercontribs-description": "All Ännerunge vun engem Benotzer kréien.",
+ "apihelp-query+watchlist-param-user": "Nëmmen Ännerunge vun dësem Benotzer opzielen.",
+ "apihelp-query+watchlist-param-excludeuser": "Ännerunge vun dësem Benotzer net opzielen.",
+ "apihelp-query+watchlistraw-param-show": "Nëmmen Elementer opzielen déi dëse Critèren entspriechen.",
+ "apihelp-query+watchlistraw-example-simple": "Säite vum aktuelle Benotzer senger Iwwerwaachungslëscht opzielen",
+ "apihelp-revisiondelete-description": "Versioune läschen a restauréieren.",
+ "apihelp-revisiondelete-param-reason": "Grond fir ze Läschen oder ze Restauréieren.",
+ "apihelp-rsd-example-simple": "Den RSD-Schema exportéieren",
+ "apihelp-unblock-description": "D'Spär vun engem Benotzer ophiewen.",
+ "apihelp-unblock-param-reason": "Grond fir d'Spär opzehiewen",
+ "apihelp-undelete-param-reason": "Grond fir ze restauréieren.",
+ "apihelp-undelete-example-page": "<kbd>Main Page</kbd> restauréieren.",
+ "apihelp-upload-param-watch": "D'Säit iwwerwaachen.",
+ "apihelp-upload-example-url": "Vun enger URL eroplueden.",
+ "apihelp-userrights-param-user": "Benotzernumm.",
+ "apihelp-userrights-param-userid": "Benotzer Id.",
+ "apihelp-userrights-param-reason": "Grond fir d'Ännerung.",
+ "apihelp-watch-example-watch": "D'Säit <kbd>Haaptsäit</kbd> iwwerwaachen.",
+ "api-help-param-deprecated": "Vereelst.",
+ "api-help-param-required": "Dëse Parameter ass obligatoresch.",
+ "api-help-examples": "{{PLURAL:$1|Beispill|Beispiler}}:"
+}
diff --git a/includes/api/i18n/ln.json b/includes/api/i18n/ln.json
new file mode 100644
index 00000000..cd117465
--- /dev/null
+++ b/includes/api/i18n/ln.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Moyogo"
+ ]
+ },
+ "apihelp-edit-example-edit": "Kobɔngisa lokásá lɔ̌kɔ́."
+}
diff --git a/includes/api/i18n/lv.json b/includes/api/i18n/lv.json
new file mode 100644
index 00000000..b24e5f63
--- /dev/null
+++ b/includes/api/i18n/lv.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Papuass"
+ ]
+ },
+ "apihelp-userrights-param-userid": "Lietotāja ID:"
+}
diff --git a/includes/api/i18n/lzh.json b/includes/api/i18n/lzh.json
new file mode 100644
index 00000000..970bea94
--- /dev/null
+++ b/includes/api/i18n/lzh.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "RalfX"
+ ]
+ },
+ "apihelp-feedcontributions-param-toponly": "僅示至新審之纂"
+}
diff --git a/includes/api/i18n/mg.json b/includes/api/i18n/mg.json
new file mode 100644
index 00000000..8e2a8b56
--- /dev/null
+++ b/includes/api/i18n/mg.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jagwar"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [https://www.mediawiki.org/wiki/API:Main_page Torohevitra be kokoa]\n* [https://www.mediawiki.org/wiki/API:FAQ Fanontaniana miverina matetika]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lisitry ny mailaka manaraka]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Filazana API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Baogy & hataka]\n</div>\n<strong>Status:</strong> \nTokony mandeha avokoa ny fitaovana aseho eto amin'ity pehy ity, na dia izany aza mbola am-panamboarana ny API ary mety hiova na oviana na oviana. Araho amin'ny alalan'ny fisoratana ny mailakao ao amin'ny [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ the mediawiki-api-announce lisitra fampielezana] ny fiovana.\n\n<strong>Hataka diso:</strong> \nRehefa alefa ao amin'i API ny hata, ho alefa miaraka amin'ny lakile \"MediaWiki-API-Error\" ny header HTTP ary samy homen-tsanda mitovy ny header ary ny kaodin-kadisoana. Ho an'ny torohay fanampiny dia jereo https://www.mediawiki.org/wiki/API:Errors_and_warnings.",
+ "apihelp-main-param-action": "Inona ny zavatra ho atao.",
+ "apihelp-main-param-format": "Format mivoaka",
+ "apihelp-createaccount-param-name": "Anaram-pikambana."
+}
diff --git a/includes/api/i18n/mk.json b/includes/api/i18n/mk.json
new file mode 100644
index 00000000..1efb45f3
--- /dev/null
+++ b/includes/api/i18n/mk.json
@@ -0,0 +1,398 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bjankuloski06"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [https://www.mediawiki.org/wiki/API:Main_page Документација]\n* [https://www.mediawiki.org/wiki/API:FAQ ЧПП]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Поштенски список]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Соопштенија за Извршникот]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Грешки и барања]\n</div>\n<strong>Статус:</strong> Сите ставки на страницава би требало да работат, но Извршникот сепак е во активна разработка, што значи дека може да се смени во секое време. Објавите за измени можете да ги дознавате ако се пријавите на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ поштенскиот список „the mediawiki-api-announce“].\n\n<strong>Погрешни барања:</strong> Кога Извршникот ќе добие погрешни барања, ќе се испрати HTTP-заглавие со клучот „MediaWiki-API-Error“ и потоа на вредностите на заглавието и шифрата на грешката што ќе се појават ќе им биде зададена истата вредност. ПОвеќе информации ќе најдете на https://www.mediawiki.org/wiki/API:Errors_and_warnings.",
+ "apihelp-main-param-action": "Кое дејство да се изврши.",
+ "apihelp-main-param-format": "Формат на изводот.",
+ "apihelp-main-param-maxlag": "Максималниот заостаток може да се користи кога МедијаВики е воспоставен на грозд умножен од базата. За да спречите дополнителни заостатоци од дејства, овој параметар му наложува на клиентот да почека додека заостатокот не се намали под укажаната вредност. Во случај на преголем заостаток, системт ја дава грешката со код „maxlag“ со порака од обликот „Го чекам $host: има заостаток од $lag секунди“.<br />Погл. https://www.mediawiki.org/wiki/Manual:Maxlag_parameter за повеќе информации.",
+ "apihelp-main-param-smaxage": "Задајте му олку секунди на заглавитето <code>s-maxage</code>. Грешките никогаш не се чуваат во меѓускладот.",
+ "apihelp-main-param-maxage": "Задајте му олку секунди на заглавитето <code>max-age</code>. Грешките никогаш не се чуваат во меѓускладот.",
+ "apihelp-main-param-assert": "Провери дали корисникот е најавен ако е зададено „user“ или дали го има корисничкото право на бот, ако е зададено „bot“.",
+ "apihelp-main-param-requestid": "Тука внесената вредност ќе биде вклучена во извештајот. Може да се користи за разликување на барањата.",
+ "apihelp-main-param-servedby": "Вклучи го домаќинското име што го услужило барањето во резултатите.",
+ "apihelp-main-param-curtimestamp": "Бклучи тековно време и време и датум во резултатот.",
+ "apihelp-main-param-origin": "Кога му пристапувате на Пирлогот користејќи повеќедоменско AJAX-барање (CORS), задајте му го на ова изворниот домен. Ова мора да се вклучи во секое подготвително барање и затоа мора да биде дел од URI на барањето (не главната содржина во POST). Ова мора точно да се совпаѓа со еден од изворниците на заглавието Origin:, така што мора да е зададен на нешто како http://en.wikipedia.org or https://meta.wikimedia.org. Ако овој параметар не се совпаѓа со заглавието Origin:, ќе се појави одговор 403. Ако се совпаѓа, а изворникот е на бел список (на допуштени), тогаш ќе се зададе ззаглавието Контрола на пристап-Изворник.",
+ "apihelp-main-param-uselang": "Јазик за преведување на пораките. Список на јазични кодови ќе најдете на [[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]] со siprop=languages или укажете „user“ за да го користите тековно зададениот јазик корисникот, или пак укажете „content“ за да го користите јазикот на содржината на ова вики.",
+ "apihelp-block-description": "Блокирај корисник.",
+ "apihelp-block-param-user": "Корисничко име, IP-адреса или IP-опсег ако сакате да блокирате.",
+ "apihelp-block-param-expiry": "Време на истек. Може да биде релативно (на пр. „5 месеци“ или „2 недели“) или пак апсолутно (на пр. „2014-09-18T12:34:56Z“). Ако го зададете „бесконечно“, „неодредено“ или „никогаш“, блокот ќе трае засекогаш.",
+ "apihelp-block-param-reason": "Причина за блокирање.",
+ "apihelp-block-param-anononly": "Блокирај само анонимни корисници (т.е. оневозможи анонимно уредување од оваа IP-адреса).",
+ "apihelp-block-param-nocreate": "Оневозможи создавање кориснички сметки.",
+ "apihelp-block-param-autoblock": "Автоматски блокирај ја последно употребената IP-адреса и сите понатамошни IP-адреси од кои лицето ќе се обиде да се најави.",
+ "apihelp-block-param-noemail": "Оневозможи му на корисникот да испаќа е-пошта преку викито. (Го бара правото „блокирање е-пошта“).",
+ "apihelp-block-param-hidename": "Скриј го корисничкото име од дневникот на блокирања. (Го бара правото „скривање корисник“)",
+ "apihelp-block-param-allowusertalk": "Овозможи му на корисникот да си ја уредува сопствената страница за разговор (зависи од <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "Ако корисникот е веќе блокиран, наметни врз постоечкиот блок.",
+ "apihelp-block-param-watchuser": "Набљудувај ја корисничката страница и страницата за разговор на овој корисник или IP-адреса",
+ "apihelp-block-example-ip-simple": "Блокирај ја IP-адресата 192.0.2.5 три дена со причината „Прва опомена“",
+ "apihelp-block-example-user-complex": "Блокирај го корисникот Вандал (Vandal) бесконечно со причината „Вандализам“ и оневозможи создавање на нови сметки и праќање е-пошта",
+ "apihelp-clearhasmsg-description": "Ја отстранува ознаката „<code>hasmsg</code>“ од тековниот корисник.",
+ "apihelp-clearhasmsg-example-1": "Отстрани ја ознаката „<code>hasmsg</code>“ од тековниот корисник",
+ "apihelp-compare-description": "Добивање на разлика помеѓу две страници.\n\nМора да се добие број на преработката, наслов на странивата или пак нејзина назнака. Важи и за „од“ и за „на“.",
+ "apihelp-compare-param-fromtitle": "Прв наслов за споредба.",
+ "apihelp-compare-param-fromid": "Прва назнака на страница за споредба.",
+ "apihelp-compare-param-fromrev": "Прва преработка за споредба.",
+ "apihelp-compare-param-totitle": "Втор наслов за споредба.",
+ "apihelp-compare-param-toid": "Втора назнака на страница за споредба.",
+ "apihelp-compare-param-torev": "Бтора преработка за споредба.",
+ "apihelp-compare-example-1": "Дај разлика помеѓу преработките 1 и 2",
+ "apihelp-createaccount-description": "Создај нова корисничка сметка.",
+ "apihelp-createaccount-param-name": "Корисничко име.",
+ "apihelp-createaccount-param-password": "Лозинка (се занемарува ако е зададено <var>$1mailpassword</var>).",
+ "apihelp-createaccount-param-domain": "Домен за надворешна заверка (незадолжително).",
+ "apihelp-createaccount-param-token": "Шифра за создавање сметка добиена во првото барање.",
+ "apihelp-createaccount-param-email": "Е-пошта на корисникот (незадолжително).",
+ "apihelp-createaccount-param-realname": "Вистинско име на корисникот (незадолжително).",
+ "apihelp-createaccount-param-mailpassword": "Ако му се зададе било каква вредност, тогаш на корисникот ќе му биде испратена случајна лозинка.",
+ "apihelp-createaccount-param-reason": "Незадолжителна прочина за создавање на сметката која ќе стои во дневниците.",
+ "apihelp-createaccount-param-language": "Јазичен код кој ќе биде стандарден за корисникот (незадолжително, по основно: јазикот на самото вики).",
+ "apihelp-createaccount-example-pass": "Создај го корисникот „testuser“ со лозинката „test123“",
+ "apihelp-createaccount-example-mail": "Создај го корисникот „testmailuser“ и испрати случајно-создадена лозинка по е-пошта",
+ "apihelp-delete-description": "Избриши страница.",
+ "apihelp-delete-param-title": "Наслов на страницата што сакате да ја избришете. Не може да се користи заедно со $1pageid.",
+ "apihelp-delete-param-pageid": "Назнака на страницата што сакате да ја избришете. Не може да се користи заедно со $1title.",
+ "apihelp-delete-param-reason": "Причина за бришење. Ако не се зададе, ќе се наведе автоматска причина.",
+ "apihelp-delete-param-watch": "Додај ја страницата во набљудуваните.",
+ "apihelp-delete-param-watchlist": "Безусловно додај или отстрани ја страницата од набљудуваните, користете ги нагодувањата или не ги менувајте набљудуваните.",
+ "apihelp-delete-param-unwatch": "Отстрани ја страницата од набљудуваните.",
+ "apihelp-delete-param-oldimage": "Името на страта слика за бришење според добиеното од [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]].",
+ "apihelp-delete-example-simple": "Избриши ја Главната страница",
+ "apihelp-delete-example-reason": "Избриши ја Главната страница со причината „Подготовка за преместување“",
+ "apihelp-disabled-description": "Модулот е деактивиран.",
+ "apihelp-edit-description": "Создај или уреди страници.",
+ "apihelp-edit-param-title": "Наслов на страницата што сакате да ја уредите. Не може да се користи заедно со $1pageid.",
+ "apihelp-edit-param-pageid": "Назнака на страницата што сакате да ја уредите. Не може да се користи заедно со $1title.",
+ "apihelp-edit-param-section": "Број на поднасловот. 0 за првиот, „new“ за нов.",
+ "apihelp-edit-param-sectiontitle": "Назив на новиот поднаслов",
+ "apihelp-edit-param-text": "Содржина на страницата.",
+ "apihelp-edit-param-summary": "Опис на уредувањето. Ова е и назив на поднасловот кога не се зададени $1section=new и $1sectiontitle.",
+ "apihelp-edit-param-minor": "Ситно уредување.",
+ "apihelp-edit-param-notminor": "Неситно уредување.",
+ "apihelp-edit-param-bot": "Означи го уредувањево како ботско.",
+ "apihelp-edit-param-basetimestamp": "Датум и време на преработката на базата, кои се користат за утврдување на спротиставености во уредувањето. Може да се добие преку [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
+ "apihelp-edit-param-starttimestamp": "Датум и време кога сте го почнале уредувањето, кои се користат за утврдување на спротиставености во уредувањата. Соодветната вредност се добива користејќи [[Special:ApiHelp/main|curtimestamp]] кога ќе почнете со уредување (на пр. кога ќе се вчита содржината што ќе ја уредувате).",
+ "apihelp-edit-param-recreate": "Занемари ги грешките што се појавуваат во врска со статијата што е избришана во меѓувреме.",
+ "apihelp-edit-param-createonly": "Не ја уредувај страницата ако веќе постои.",
+ "apihelp-edit-param-nocreate": "Дај грешка ако страницата не постои.",
+ "apihelp-edit-param-watch": "Додај ја страницата во набљудуваните.",
+ "apihelp-edit-param-unwatch": "Отстрани ја страницата од набљудуваните.",
+ "apihelp-edit-param-watchlist": "Безусловно додај или отстрани ја страницата од набљудуваните, користете ги нагодувањата или не ги менувајте набљудуваните.",
+ "apihelp-edit-param-md5": "MD5-тарабата на параметарот $1text, или параметрите $1prependtext и $1appendtext поврзани. Ако е зададено, уредувањето нема да се изврши без тарабата да биде исправна.",
+ "apihelp-edit-param-prependtext": "Ставете го текстов на почетокот од страницата. Го заменува $1text.",
+ "apihelp-edit-param-appendtext": "Ставете го текстов на крајот од страницата. Го заменува $1text.\n\nКористете $1section=new наместо овој параметар за да приложите кон новиот поднаслов.",
+ "apihelp-edit-param-undo": "Отповикај ја преработкава. Ги заменува $1text, $1prependtext и $1appendtext.",
+ "apihelp-edit-param-undoafter": "Отповикај ги преработките од $1undo до оваа. Ако не е зададено, отповикај само една.",
+ "apihelp-edit-param-redirect": "Автоматски решавај пренасочувања.",
+ "apihelp-edit-param-contentformat": "Форматот за серијализација на содржината што се користи во вносниот текст.",
+ "apihelp-edit-param-contentmodel": "Содржински модел на новата содржина.",
+ "apihelp-edit-param-token": "Шифрата треба секогаш да се испраќа како последниот параметар, или барем по параметарот $1text.",
+ "apihelp-edit-example-edit": "Уреди страница",
+ "apihelp-edit-example-prepend": "Стави <kbd>_&#95;NOTOC_&#95;</kbd> пред страницата",
+ "apihelp-edit-example-undo": "Отповикај ги преработките од 13579 до 13585 со автоматски опис",
+ "apihelp-emailuser-description": "Испрати е-пошта на корисник.",
+ "apihelp-emailuser-param-target": "На кој корисник да му се испрати е-поштата.",
+ "apihelp-emailuser-param-subject": "Наслов.",
+ "apihelp-emailuser-param-text": "Содржина.",
+ "apihelp-emailuser-param-ccme": "Прати ми примерок и мене.",
+ "apihelp-emailuser-example-email": "Испрати е-пошта на корисникот „WikiSysop“ со текстот „Содржина“",
+ "apihelp-expandtemplates-description": "Ги проширува сите шаблони во викитекст.",
+ "apihelp-expandtemplates-param-title": "Наслов на страница.",
+ "apihelp-expandtemplates-param-text": "Викитекст за претворање.",
+ "apihelp-expandtemplates-param-revid": "Назнака на преработката, за <nowiki>{{REVISIONID}}</nowiki> и слични променливи.",
+ "apihelp-expandtemplates-param-prop": "Кои информации треба да ги добиете:\n;wikitext:The expanded wikitext.\n;categories: Категориите присутно во вносот кои не се претставени во викитекстуалниот извод.\n;volatile: Дали изводот е месно врзан и не треба да се преупотребува на други места во страницата.\n;ttl: Максималното време по кое треба да се поништи меѓускладираниот резултат.\n;parsetree: XML-дрвото на расчленување за изводот.\nИмајте на ум дека ако не изберете никаква вредност, резултатот ќе го содржи викитекстот, но изводот ќе биде во застарен формат.",
+ "apihelp-expandtemplates-param-includecomments": "Дали во изводот да се вклучени HTML-коментари.",
+ "apihelp-expandtemplates-param-generatexml": "Создај XML-дрво на расчленување (заменето со $1prop=parsetree).",
+ "apihelp-expandtemplates-example-simple": "Прошири го викитекстот „<nowiki>{{Project:Sandbox}}</nowiki>“",
+ "apihelp-feedcontributions-description": "Дава канал со придонеси на корисник.",
+ "apihelp-feedcontributions-param-feedformat": "Формат на каналот.",
+ "apihelp-feedcontributions-param-user": "За кои корисници да се прикажуваат придонесите.",
+ "apihelp-feedcontributions-param-namespace": "По кој именски простор да се филтрираат придонесите:",
+ "apihelp-feedcontributions-param-year": "Од година (и порано):",
+ "apihelp-feedcontributions-param-month": "Од месец (и порано):",
+ "apihelp-feedcontributions-param-tagfilter": "Филтрирај придонеси што имаат ознаки.",
+ "apihelp-feedcontributions-param-deletedonly": "Прикажувај само избришани придонеси.",
+ "apihelp-feedcontributions-param-toponly": "Прикажувај само последни преработки.",
+ "apihelp-feedcontributions-param-newonly": "Прикажувај само новосоздадени страници",
+ "apihelp-feedcontributions-param-showsizediff": "Покажувај ја големинската разлика меѓу преработките.",
+ "apihelp-feedcontributions-example-simple": "Покажувај придонеси на [[Корисник:Пример]]",
+ "apihelp-feedrecentchanges-description": "Дава канал со скорешни промени.",
+ "apihelp-feedrecentchanges-param-feedformat": "Форматот на каналот.",
+ "apihelp-feedrecentchanges-param-namespace": "На кој именски простор да се ограничат резултатите.",
+ "apihelp-feedrecentchanges-param-invert": "Сите именски простори освен избраниот.",
+ "apihelp-feedrecentchanges-param-associated": "Вклучи придружни именски простори (разговор или главен).",
+ "apihelp-feedrecentchanges-param-days": "На кои денови да се ограничат резултатите.",
+ "apihelp-feedrecentchanges-param-limit": "Максималниот број на резултати за прикажување.",
+ "apihelp-feedrecentchanges-param-from": "Прикажи ги промените оттогаш.",
+ "apihelp-feedrecentchanges-param-hideminor": "Скриј ги ситните промени.",
+ "apihelp-feedrecentchanges-param-hidebots": "Скриј ги промените напарвени од ботови.",
+ "apihelp-feedrecentchanges-param-hideanons": "Скриј ги промените направени од анонимни корисници.",
+ "apihelp-feedrecentchanges-param-hideliu": "Скриј ги промените направени од регистрирани корисници.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Скриј ги испатролираните промени.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Скриј ги моите промени.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Филтрирање по ознака.",
+ "apihelp-feedrecentchanges-param-target": "Прикажи само промени на страници што водат од оваа.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Наместо тоа, прикажи ги промените на страниците поврзани со избраната страница.",
+ "apihelp-feedrecentchanges-example-simple": "Прикажи скорешни промени",
+ "apihelp-feedrecentchanges-example-30days": "Прикажувај скорешни промени 30 дена",
+ "apihelp-feedwatchlist-description": "Дава канал од набљудуваните.",
+ "apihelp-feedwatchlist-param-feedformat": "Форматот на каналот.",
+ "apihelp-feedwatchlist-param-hours": "Испиши страници изменети во рок од олку часови отсега.",
+ "apihelp-feedwatchlist-param-linktosections": "Давај ме право на изменетите делови, ако е можно.",
+ "apihelp-feedwatchlist-example-default": "Прикажи го каналот од набљудуваните.",
+ "apihelp-feedwatchlist-example-all6hrs": "Прикажи ги сите промени во набљудуваните во последните 6 часа",
+ "apihelp-filerevert-description": "Врати податотека на претходна верзија.",
+ "apihelp-filerevert-param-filename": "Име на целната податотека, без претставката „Податотека:“.",
+ "apihelp-filerevert-param-comment": "Коментар за подигањето.",
+ "apihelp-filerevert-param-archivename": "Архивски назив на преработката што ја повраќате.",
+ "apihelp-filerevert-example-revert": "Врати ја <kbd>Wiki.png</kbd> на верзијата од <kbd>2011-03-05T15:27:40Z</kbd>",
+ "apihelp-help-description": "Прикажувај помош за укажаните модули.",
+ "apihelp-help-param-modules": "Модули за приказ на помош за (вредности на параметрите action= и format=, или пак „main“). Може да се укажат подмодули со „+“.",
+ "apihelp-help-param-submodules": "Прикажувај и помош за подмодули на именуваниот модул.",
+ "apihelp-help-param-recursivesubmodules": "Прикажувај и помош за подмодули рекурзивно.",
+ "apihelp-help-param-helpformat": "Формат на изводот на помошта.",
+ "apihelp-help-param-wrap": "Обвиткај го изводот како станрадна одѕивна структура од прилотот.",
+ "apihelp-help-param-toc": "Вклучи табела со содржина во HTML-изводот.",
+ "apihelp-help-example-main": "Помош за главниот модул",
+ "apihelp-help-example-recursive": "Сета помош на една страница",
+ "apihelp-help-example-help": "Помош за самиот помошен модул",
+ "apihelp-help-example-query": "Помош за два подмодула за барања",
+ "apihelp-imagerotate-description": "Сврти една или повеќе слики.",
+ "apihelp-imagerotate-param-rotation": "За колку степени да се сврти надесно.",
+ "apihelp-imagerotate-example-simple": "Сврти ја [[:Податотека:Пример.png]] за 90 степени",
+ "apihelp-imagerotate-example-generator": "Сврти ги сите слики во [[:Категорија:Некоја]] за 180 степени",
+ "apihelp-import-description": "Увези страница од друго вики или XML-податотека.\n\nИмајте на ум дека POST на HTTP мора да се изведе како подигање на податотеката (т.е. користејќи повеќеделни податоци/податоци од образец) кога ја испраќате податотеката за параметарот „xml“.",
+ "apihelp-import-param-summary": "Увези опис.",
+ "apihelp-import-param-xml": "Подигната XML-податотека.",
+ "apihelp-import-param-interwikisource": "За меѓујазични увози: од кое вики да се увезе.",
+ "apihelp-import-param-interwikipage": "За меѓујазични увози: страница за увоз.",
+ "apihelp-import-param-fullhistory": "За меѓујазични увози:: увези ја целата историја, а не само тековната верзија.",
+ "apihelp-import-param-templates": "За меѓујазични увози: увези ги и сите вклучени шаблони.",
+ "apihelp-import-param-namespace": "За меѓујазични увози: увези во овој именски простор.",
+ "apihelp-import-param-rootpage": "Увези како потстраница на страницава.",
+ "apihelp-import-example-import": "Увези [[meta:Help:Parserfunctions]] во именскиот простор 100 со целата историја.",
+ "apihelp-login-description": "Најавете се и добијте колачиња за заверка.\n\nВо случај кога ќе се најавите успешно, потребните колачиња ќе се придодадат кон заглавијата на HTTP-одѕивот. Во случај да не успеете да се најавите, понатамошните обиди може да се ограничат за да се ограничат нападите со автоматизирано погодување на лозинката.",
+ "apihelp-login-param-name": "Корисничко име.",
+ "apihelp-login-param-password": "Лозинка.",
+ "apihelp-login-param-domain": "Домен (незадолжително).",
+ "apihelp-login-param-token": "Најавна шифра добиена со првото барање.",
+ "apihelp-login-example-gettoken": "Набави најавна шифра.",
+ "apihelp-login-example-login": "Најава",
+ "apihelp-logout-description": "Одјави се и исчисти ги податоците на седницата.",
+ "apihelp-logout-example-logout": "Одјави го тековниот корисник",
+ "apihelp-move-description": "Премести страница.",
+ "apihelp-move-param-from": "Наслов на страницата што сакате да ја преместите. Не може да се користи заедно со $1fromid.",
+ "apihelp-move-param-fromid": "Назнака на страницата што сакате да ја преместите. Не може да се користи заедно со $1from.",
+ "apihelp-move-param-to": "Како сакате да гласи новиот наслов на страницата.",
+ "apihelp-move-param-reason": "Причина за преместувањето.",
+ "apihelp-move-param-movetalk": "Премести ја и страницата за разговор, ако ја има.",
+ "apihelp-move-param-movesubpages": "Премести потстраници, ако има",
+ "apihelp-move-param-noredirect": "Не прави пренасочување.",
+ "apihelp-move-param-watch": "Додај ги страницата и пренасочувањето во набљудуваните.",
+ "apihelp-move-param-unwatch": "Отстрани ги страницата и пренасочувањето од набљудуваните.",
+ "apihelp-move-param-watchlist": "Безусловно додај или отстрани ја страницата од набљудуваните, користете ги нагодувањата или не ги менувајте набљудуваните.",
+ "apihelp-move-param-ignorewarnings": "Занемари предупредувања.",
+ "apihelp-move-example-move": "Премести го „Лош наслов“ на „Добар наслов“, неоставајќи пренасочување",
+ "apihelp-opensearch-description": "Пребарување на викито со протоколот OpenSearch.",
+ "apihelp-opensearch-param-search": "Низа за пребарување.",
+ "apihelp-opensearch-param-limit": "Максималниот број на резултати за прикажување.",
+ "apihelp-opensearch-param-namespace": "Именски простори за пребарување.",
+ "apihelp-opensearch-param-suggest": "Не прави ништо ако [https://www.mediawiki.org/wiki/Manual:$wgEnableOpenSearchSuggest $wgEnableOpenSearchSuggest] е неточно.",
+ "apihelp-opensearch-param-redirects": "Како да се работи со пренасочувања:\n;return: Дај го самото пренасочување.\n;resolve: Дај ја целната страница. Може да даде помалку од $1limit резултати.\nОд историски причини, по основно е „return“ за $1format=json и „resolve“ за други формати.",
+ "apihelp-opensearch-param-format": "Формат на изводот.",
+ "apihelp-opensearch-example-te": "Најди страници што почнуваат со „Те“",
+ "apihelp-options-description": "Смени ги нагодувањата на тековниот корисник.\n\nМожат да се зададат само можностите заведени во јадрото или во едно од воспоставените додатоци, или пак можности со клуч кој ја има претставката „userjs-“ (предвиден за употреба од кориснички скрипти).",
+ "apihelp-options-param-reset": "Ги враќа поставките по основно.",
+ "apihelp-options-param-resetkinds": "Писок на типови можности за повраток кога е зададена можноста „$1reset“.",
+ "apihelp-options-param-change": "Список на промени во форматот name=value (на пр. skin=vector). Вредностите не треба да содржат исправени црти. Ако не зададете вредност (дури ни знак за равенство), на пр., можност|другаможност|..., ќе биде зададена вредноста на можноста по основно.",
+ "apihelp-options-param-optionname": "Назив на можноста што треба да ѝ се зададе на вредноста дадена од „$1optionvalue“.",
+ "apihelp-options-param-optionvalue": "Вредноста на можноста укажана од „$1optionnam“. Може да содржи исправени црти.",
+ "apihelp-options-example-reset": "Врати ги сите поставки по основно",
+ "apihelp-options-example-change": "Смени ги поставките „skinЗ“ и „hideminor“",
+ "apihelp-options-example-complex": "Врати ги сите нагодувања по основно, а потоа задај ги „skin“ и „nickname“",
+ "apihelp-paraminfo-description": "Набави информации за извршнички (API) модули.",
+ "apihelp-paraminfo-param-modules": "Список на називи на модули (вредности на параметрите action= и format=, или пак „main“). Може да се укажат подмодули со „+“.",
+ "apihelp-paraminfo-param-helpformat": "Формат на помошните низи.",
+ "apihelp-paraminfo-param-querymodules": "Список на називи на модули за барања (вредност на параметарот prop=, meta= или list=). Користете го „$1modules=query+foo“ наместо „$1querymodules=foo“.",
+ "apihelp-paraminfo-param-mainmodule": "Добави информации и за главниот (врховен) модул. Користете го „$1modules=main“ наместо тоа.",
+ "apihelp-paraminfo-param-pagesetmodule": "Дај ги сите информации и за модулот на збирот страници (укажувајќи titles= и сродни).",
+ "apihelp-paraminfo-param-formatmodules": "Список на називи на форматни модули (вредностза параметарот format=). Наместо тоа, користете го „$1modules“.",
+ "apihelp-parse-param-summary": "Опис за расчленување.",
+ "apihelp-parse-param-preview": "Расчлени во прегледен режим.",
+ "apihelp-parse-param-sectionpreview": "Расчлени во прегледен режим на поднасловот (го овозможува и прегледниот режим).",
+ "apihelp-parse-param-disabletoc": "Изземи го преглед на содржината во изводеот.",
+ "apihelp-parse-param-contentformat": "Формат на серијализацијата на содржината во вносниот текст. Важи само кога се користи со $1text.",
+ "apihelp-parse-example-page": "Расчлени страница.",
+ "apihelp-parse-example-text": "Расчлени викитекст.",
+ "apihelp-parse-example-texttitle": "Расчлени страница, укажувајќи го насловот на страницата.",
+ "apihelp-parse-example-summary": "Расчлени опис.",
+ "apihelp-patrol-description": "Испатролирај страница или ревизија.",
+ "apihelp-patrol-param-rcid": "Назнака на спорешните промени за патролирање.",
+ "apihelp-patrol-param-revid": "Назнака на преработката за патролирање.",
+ "apihelp-patrol-example-rcid": "Испатролирај скорешна промена",
+ "apihelp-patrol-example-revid": "Патролирај праработка",
+ "apihelp-protect-description": "Смени го степенот на заштита на страница.",
+ "apihelp-protect-param-title": "Наслов на страница што се (од)заштитува. Не може да се користи заедно со $1pageid.",
+ "apihelp-protect-param-pageid": "Назнака на страница што се (од)заштитува. Не може да се користи заедно со $1title.",
+ "apihelp-protect-param-reason": "Причиина за (од)заштитување",
+ "apihelp-protect-example-protect": "Заштити страница",
+ "apihelp-purge-param-forcelinkupdate": "Поднови ги табелите со врски.",
+ "apihelp-purge-example-simple": "Превчитај ги „Главна страница“ и „Извршник“",
+ "apihelp-query-param-list": "Кои списоци да се набават.",
+ "apihelp-query-param-meta": "Кои метаподатоци да се набават.",
+ "apihelp-query+allcategories-description": "Наброј ги сите категории.",
+ "apihelp-query+allcategories-param-from": "Од која категорија да почне набројувањето.",
+ "apihelp-query+allcategories-param-to": "На која категорија да запре набројувањето.",
+ "apihelp-query+allcategories-param-dir": "Насока на подредувањето.",
+ "apihelp-query+alldeletedrevisions-param-from": "Почни го исписот од овој наслов.",
+ "apihelp-query+alldeletedrevisions-param-to": "Запри го исписот на овој наслов.",
+ "apihelp-query+alldeletedrevisions-example-user": "Список на последните 50 избришани придонеси на Корисник:Пример",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Список на последните 50 избришани преработки во главниот именски простор",
+ "apihelp-query+allimages-example-B": "Прикажи список на податотеки што почнуваат со буквата „Б“",
+ "apihelp-query+allimages-example-recent": "Прикажи список на неодамна подигнати податотеки сличен на [[Special:NewFiles]]",
+ "apihelp-query+allimages-example-generator": "Прикажи информации за околу 4 податотеки што почнуваат со буквата „Т“",
+ "apihelp-query+alllinks-description": "Наброј ги сите врски што водат кон даден именски простор.",
+ "apihelp-query+alllinks-param-from": "Наслов на врската од која ќе почне набројувањето.",
+ "apihelp-query+alllinks-param-to": "Наслов на врската на која ќе запре набројувањето.",
+ "apihelp-query+alllinks-param-prefix": "Пребарај ги сите сврзани наслови што почнуваат со оваа вредност.",
+ "apihelp-query+alllinks-param-unique": "Прикажувај само различни поврзани наслови. Не може да се користи со $1prop=ids.\nКога се користи како создавач, дава целни страници наместо изворни.",
+ "apihelp-query+alllinks-param-namespace": "Именскиот простор што се набројува.",
+ "apihelp-query+alllinks-param-limit": "Колку вкупно ставки да се дадат.",
+ "apihelp-query+alllinks-param-dir": "Насока на исписот.",
+ "apihelp-query+alllinks-example-B": "Списока на наслови со врски, вклучувајќи ги отсутните, со назнаки на нивните страници, почнувајќи од Б",
+ "apihelp-query+alllinks-example-unique": "Испиши единствени наслови со врски",
+ "apihelp-query+alllinks-example-unique-generator": "Ги дава сите наслови со врски, означувајќи ги отсутните",
+ "apihelp-query+alllinks-example-generator": "Дава страници што ги содржат врските",
+ "apihelp-query+allmessages-description": "Дава пораки од ова мрежно место.",
+ "apihelp-query+allmessages-param-prop": "Кои својства да се дадат.",
+ "apihelp-query+allmessages-param-filter": "Дај само пораки со називи што ја содржат оваа низа.",
+ "apihelp-query+allmessages-param-customised": "Дај само пораки во оваа состојба на прилагоденост.",
+ "apihelp-query+allmessages-param-lang": "Дај само пораки на овој јазик.",
+ "apihelp-query+allmessages-param-from": "Дај ги пораките што почнуваат од оваа порака.",
+ "apihelp-query+allmessages-param-to": "Дај пораки што завршуваат со оваа порака.",
+ "apihelp-query+allmessages-param-title": "Назив на страницата што ќе се користи во контекст кога се расчленува порака (за можноста $1enableparser).",
+ "apihelp-query+allmessages-param-prefix": "Дај пораки со оваа претставка.",
+ "apihelp-query+allmessages-example-ipb": "Прикажи ги пораките што започнуваат со „ipb-“",
+ "apihelp-query+allmessages-example-de": "Прикажи ги пораките „август“ и „главна страница“ на германски",
+ "apihelp-query+allpages-description": "Наброј ги сите страници последователно во даден именски простор.",
+ "apihelp-query+allpages-param-from": "Наслов на страницата од која ќе почне набројувањето.",
+ "apihelp-query+allpages-param-to": "Наслов на страницата на која ќе запре набројувањето.",
+ "apihelp-query+allpages-param-prefix": "Пребарај ги сите наслови на страници што почнуваат со оваа вредност.",
+ "apihelp-query+allpages-param-namespace": "Именскиот простор што се набројува.",
+ "apihelp-query+allpages-param-filterredir": "Кои страници да се испишат.",
+ "apihelp-query+allpages-param-minsize": "Ограничи на страници со барем олку бајти.",
+ "apihelp-query+allpages-param-maxsize": "Ограничи на страници со највеќе олку бајти.",
+ "apihelp-query+allpages-param-prtype": "Ограничи на само заштитени страници.",
+ "apihelp-query+backlinks-example-simple": "Прикажи врски до [[Главна страница|Главната страница]]",
+ "apihelp-query+backlinks-example-generator": "Дава информации за страниците што водат до [[Главна страница|Главната страница]]",
+ "apihelp-query+blocks-description": "Список на сите блокирани корисници и IP-адреси",
+ "apihelp-query+blocks-param-start": "Од кој датум и време да почне набројувањето.",
+ "apihelp-query+blocks-param-end": "На кој датум и време да запре набројувањето.",
+ "apihelp-query+blocks-param-ids": "Список на назнаки на блоковите за испис (незадолжително)",
+ "apihelp-query+blocks-param-users": "Список на корисници што ќе се пребаруваат (незадолжително)",
+ "apihelp-query+imageinfo-param-urlheight": "Слично на $1urlwidth.",
+ "apihelp-query+revisions-example-last5": "Дај ги последните 5 преработки на „Главна страница“",
+ "apihelp-query+revisions-example-first5": "Дај ги првите 5 преработки на „Главна страница“",
+ "apihelp-query+revisions-example-first5-after": "Дај ги првите 5 преработки на „Главна страница“ направени по 2006-05-01 (1 мај 2006 г.)",
+ "apihelp-query+revisions-example-first5-not-localhost": "Дај ги првите 5 преработки на „Главна страница“ кои не се направени од анонимниот корисник „127.0.0.1“",
+ "apihelp-query+revisions-example-first5-user": "Дај ги првите 5 преработки на „Главна страница“ кои се направени од корисникот „зададен од МедијаВики“ (MediaWiki default)",
+ "apihelp-query+search-example-simple": "Побарај „meaning“",
+ "apihelp-query+search-example-text": "Побарај го „meaning“ по текстовите",
+ "apihelp-query+search-example-generator": "Дај информации за страниците што излегуваат во резултатите од пребарувањето на „meaning“",
+ "apihelp-query+siteinfo-description": "Дај општи информации за мрежното место.",
+ "apihelp-upload-param-filename": "Целно име на податотеката.",
+ "apihelp-upload-param-comment": "Коментар при подигање. Се користи и како првичен текст на страницата за нови податотеки ако не е укажано „$1text“.",
+ "apihelp-upload-param-text": "Првичен текст на страницата за нови податотеки.",
+ "apihelp-upload-param-watch": "Набљудувај ја страницата.",
+ "apihelp-upload-param-watchlist": "Безусловно додај или отстрани ја страницата од набљудуваните, користете ги нагодувањата или не ги менувајте набљудуваните.",
+ "apihelp-upload-param-ignorewarnings": "Занемари предупредувања.",
+ "apihelp-upload-param-file": "Содржина на податотеката.",
+ "apihelp-upload-param-url": "Од која URL-адреса да се преземе податотеката.",
+ "apihelp-upload-param-filekey": "Клуч на претходното подигање кое е привремено складирано.",
+ "apihelp-upload-param-sessionkey": "Исто што и $1filekey. Се одржува за назадна складност.",
+ "apihelp-upload-param-stash": "Ако е зададено, опслужувачот ќе ја стави податотеката на привремено чување наместо да го додаде во складиштето.",
+ "apihelp-upload-param-filesize": "Големина на целото подигање.",
+ "apihelp-upload-param-offset": "Зафатнина на делот во бајти.",
+ "apihelp-upload-param-chunk": "Содржина на делот.",
+ "apihelp-upload-param-async": "Направи ги работите со потенцијално големи податотеки неусогласени, кога е можно.",
+ "apihelp-upload-param-asyncdownload": "Направи го добивањето на URL-адреса неусогласено.",
+ "apihelp-upload-param-leavemessage": "Ако се користи неусогласено преземање, остави порака на страницата за разговор на корисникот ако е готово.",
+ "apihelp-upload-param-statuskey": "Дај ја состојбата на подигнатост за овој податотечен клуч (подигање по URL-адреса).",
+ "apihelp-upload-param-checkstatus": "Дај ја состојбата на подигнатост само за дадениот податотечен клуч.",
+ "apihelp-upload-example-url": "Подигни од URL-адреса",
+ "apihelp-userrights-param-userid": "Корисничка назнака.",
+ "apihelp-userrights-param-add": "Стави го корисникот во следниве групи.",
+ "apihelp-userrights-param-remove": "Отстрани го корисникот од следниве групи.",
+ "apihelp-userrights-param-reason": "Причина за промената.",
+ "apihelp-watch-example-watch": "Набљудувај ја страницата „Главна страница“",
+ "apihelp-watch-example-unwatch": "Отстрани ја страницата „Главна страница“ од набљудуваните",
+ "apihelp-watch-example-generator": "Набљудувај ги првите неколку страници во главниот именски простор",
+ "apihelp-format-example-generic": "Форматирај го резултатот од барањето во $1-формат",
+ "apihelp-dbg-description": "Давај го изводот во PHP-форматот var_export().",
+ "apihelp-dbgfm-description": "Давај го изводот во PHP-форматот var_export() (подобрен испис во HTML).",
+ "apihelp-dump-description": "Давај го изводот во PHP-форматот var_dump().",
+ "apihelp-dumpfm-description": "Давај го изводот во PHP-форматот var_dump() (подобрен испис во HTML).",
+ "apihelp-json-description": "Давај го изводот во JSON-формат.",
+ "apihelp-json-param-callback": "Ако е укажано, го обвива изводот во даден повик на функција. За безбедност, ќе се ограничат сите податоци што се однесуваат на корисниците.",
+ "apihelp-json-param-utf8": "Ако е укажано, ја ги шифрира највеќето (но не сите) не-ASCII знаци како UTF-8 наместо да ги заменува со хексадецимални изводни низи.",
+ "apihelp-jsonfm-description": "Давај го изводот во JSON-формат (подобрен испис во HTML).",
+ "apihelp-none-description": "Де давај извод.",
+ "apihelp-php-description": "Давај го изводот во серијализиран PHP-формат.",
+ "apihelp-phpfm-description": "Давај го изводот во серијализиран PHP-формат (подобрен испис во HTML).",
+ "apihelp-rawfm-description": "Давај го изводот со елементи за отстранување грешки во JSON-формат (подобрен испис во HTML).",
+ "apihelp-txt-description": "Давај го изводот во PHP-форматот print_r().",
+ "apihelp-txtfm-description": "Давај го изводот во PHP-форматот print_r() (подобрен испис во HTML).",
+ "apihelp-wddx-description": "Давај го изводот во WDDX-формат.",
+ "apihelp-wddxfm-description": "Давај го изводот во WDDX-формат (подобрен испис во HTML).",
+ "apihelp-xml-description": "Давај го изводот во XML-формат.",
+ "apihelp-xml-param-xslt": "Ако е укажано, додава &lt;xslt&gt; како стилска страница. Ова треба да е викистраница во именскиот простор МедијаВики (MediaWiki) чиј наслов завршува со „.xsl“.",
+ "apihelp-xml-param-includexmlnamespace": "Ако е укажано, додава именски простор XML.",
+ "apihelp-xmlfm-description": "Давај го изводот во XML-формат (подобрен испис во HTML).",
+ "apihelp-yaml-description": "Давај го изводот во YAML-формат.",
+ "apihelp-yamlfm-description": "Давај го изводот во YAML-формат (подобрен испис во HTML).",
+ "api-format-title": "Резултат од Извршникот на МедијаВики",
+ "api-format-prettyprint-header": "Ја гледате HTML-претставата на форматот $1. HTML е добар за отстранување на грешки, но не е погоден за употреба во извршник.\n\nУкажете го параметарот за формат за да го смените изводниот формат. За да ги видите претставите на форматот $1 вон HTML, задајте format=$2.\n\nПовеќе информации ќе најдете на [https://www.mediawiki.org/wiki/API целосната документација], или пак [[Special:ApiHelp/main|помош со извршникот]].",
+ "api-orm-param-props": "Полиња за пребарување.",
+ "api-orm-param-limit": "Макс. број на редови во изводот.",
+ "api-pageset-param-titles": "Список на наслови на кои ќе се работи",
+ "api-pageset-param-pageids": "Список на назнаки за страници на кои ќе се работи",
+ "api-pageset-param-revids": "Список на назнаки на преработки на кои ќе се работи",
+ "api-pageset-param-generator": "Дај го списокот на страници на кои ќе се работи исполнувајќи го укажаниот модул за барање.\n\n'''НАПОМЕНА:''' називите на создавачките параметри мора да ја имаат претставката „g“. Погледајте ги примерите.",
+ "api-help-title": "Помош со Извршникот на МедијаВики",
+ "api-help-lead": "Ова е самосоздадена документациска страница за извршникот на МедијаВики.\n\nДокументација и примери: https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "Главен модул",
+ "api-help-flag-deprecated": "Овој модул е застарен.",
+ "api-help-flag-internal": "<strong>Овој модул е внатрешен или нестабилен.</strong> Работењето може да му се промени без предупредување.",
+ "api-help-flag-readrights": "За овој модул се потребни права на читање.",
+ "api-help-flag-writerights": "За овој модул се потребни права на пишување.",
+ "api-help-flag-mustbeposted": "Овој модул прифаќа само POST-барања.",
+ "api-help-flag-generator": "Овој модул може да се користи како создавач.",
+ "api-help-parameters": "{{PLURAL:$1|Параметар|Параметри}}:",
+ "api-help-param-deprecated": "Застарен.",
+ "api-help-param-required": "Овој параметар е задолжителен.",
+ "api-help-param-list": "{{PLURAL:$1|1=Една вредност|2=Вредности (одделени со „{{!}}“)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Мора да биде празно|Може да биде празно или $2}}",
+ "api-help-param-limit": "Не се допушта повеќе од $1.",
+ "api-help-param-limit2": "Не се допушта повеќе од $1 ($2 за ботови).",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=Вредноста не може да изнесува|2=Вредностите не може да изнесуваат}} помалку од $2.",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=Вредноста не може да изнесува|2=Вредностите е може да изнесуваат}} повеќе од $3.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=Вредноста мора да изнесува|2=Вредностите мораат да изнесуваат}} помеѓу $2 и $3.",
+ "api-help-param-upload": "Мора да биде објавено како податотечно подигање користејќи податоци кои се повеќеделни или од образец.",
+ "api-help-param-multi-separate": "Одделувајте ги вредностите со „|“.",
+ "api-help-param-multi-max": "Максималниот број на вредности изнесува {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} за ботови).",
+ "api-help-param-default": "По основно: $1",
+ "api-help-param-default-empty": "По основно: <span class=\"apihelp-empty\">(празно)</span>",
+ "api-help-param-token": "Шифра „$1“ добиена од [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]",
+ "api-help-param-token-webui": "За складност, се прифаќа и шифрата што се користи за обичниот кориснички посредник.",
+ "api-help-param-disabled-in-miser-mode": "Исклучено поради [https://www.mediawiki.org/wiki/Manual:$wgMiserMode скржавиот режим].",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(нема опис)</span>",
+ "api-help-examples": "{{PLURAL:$1|Пример|Примери}}:",
+ "api-help-permissions": "{{PLURAL:$1|Дозвола|Дозволи}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Доделена на}: $2",
+ "api-help-right-apihighlimits": "Уоптреба на повисоки ограничувања за приложни барања (бавни барања: $1; брзи барања: $2). Ограничувањата за бавни барања важат и за повеќевредносни параметри.",
+ "api-credits-header": "Признанија",
+ "api-credits": "Разработувачи на Извршникот:\n* Роан Катау (главен резработувач од септември 2007 до 2009 г.)\n* Виктор Василев\n* Брајан Тонг Мињ\n* Сем Рид\n* Јуриј Астрахан (создавач, главен разработувач од септември 2006 до септември 2007 г.)\n* Brad Jorsch (главен разработувач од 2013 г. до денес)\n\nВашите коментари, предлози и прашања испраќајте ги на mediawiki-api@lists.wikimedia.org\nа грешките пријавувајте ги на https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/ms.json b/includes/api/i18n/ms.json
new file mode 100644
index 00000000..041cf2c0
--- /dev/null
+++ b/includes/api/i18n/ms.json
@@ -0,0 +1,61 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anakmalaysia"
+ ]
+ },
+ "apihelp-main-param-action": "Tindakan mana untuk dilakukan.",
+ "apihelp-main-param-format": "Format output.",
+ "apihelp-main-param-uselang": "Bahasa yang hendak digunakan untuk penterjemahan mesej. Senarai kod boleh diperoleh dari [[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo&siprop=languages]], ataupun menyatakan \"user\" untuk menggunakan bahasa kegemaran pengguna semasa.",
+ "apihelp-expandtemplates-example-simple": "Perluaskan \"<nowiki>{{Project:Sandbox}}</nowiki>\" wikiteks",
+ "apihelp-help-param-helpformat": "Format output bantuan.",
+ "apihelp-help-example-main": "Bantuan untuk modul utama",
+ "apihelp-help-example-recursive": "Segala bantuan dalam satu halaman",
+ "apihelp-help-example-help": "Bantuan untuk modul bantuan",
+ "apihelp-query+imageinfo-paramvalue-prop-thumbmime": "Menambahkan jenis MIME thumbnail imej (memerlukan url dan param $1urlwidth).",
+ "apihelp-query+prefixsearch-param-offset": "Bilangan hasil untuk dilangkau.",
+ "apihelp-query+usercontribs-param-show": "Hanya paparkan item-item yang mematuhi kriteria ini, cth. suntingan selain yang kecil sahaja: $2show=!minor.\n\nJika ditetapkannya $2show=patrolled atau $2show=!patrolled, maka semakan-semakan yang lebih lama daripada [https://www.mediawiki.org/wiki/Manual:$wgRCMaxAge $wgRCMaxAge] ($1 saat) tidak akan dipaparkan.",
+ "apihelp-userrights-param-userid": "ID pengguna.",
+ "apihelp-dbgfm-description": "Data output dalam format var_export() PHP (''pretty-print'' dalam HTML).",
+ "apihelp-dump-description": "Output data dalam format var_dump() PHP.",
+ "apihelp-dumpfm-description": "Data output dalam format var_dump() PHP (''pretty-print'' dalam HTML).",
+ "apihelp-json-description": "Data output dalam format JSON.",
+ "apihelp-json-param-utf8": "Jika dinyatakan, mengekodkan kenanyakan (tetapi bukan semua) aksara bukan ASCII sebagai UTF-8 daripada menggantikannya dengan jujukan lepasan perenambelasan.",
+ "apihelp-jsonfm-description": "Output data dalam format JSON (''pretty-print'' dalam HTML).",
+ "apihelp-php-description": "Data output dalam format PHP bersiri.",
+ "apihelp-txt-description": "Data output dalam format print_r() PHP.",
+ "apihelp-txtfm-description": "Data output dalam format print_r() PHP (''pretty-print'' dalam HTML).",
+ "apihelp-wddx-description": "Data output dalam format WDDX.",
+ "apihelp-wddxfm-description": "Output data dalam format WDDX (''pretty-print'' dalam HTML).",
+ "apihelp-xml-description": "Data output dalam format XML.",
+ "apihelp-xmlfm-description": "Data output dalam format XML (''pretty-print'' dalam HTML).",
+ "apihelp-yaml-description": "Data output dalam format YAML.",
+ "apihelp-yamlfm-description": "Output data dalam format YAML (''pretty-print'' dalam HTML).",
+ "api-format-title": "Hasil API MediaWiki",
+ "api-format-prettyprint-header": "Anda sedang menyaksikan representasi format $1 dalam bentuk HTML. HTML bagus untuk menyah pepijat, tetapi tidak sesuai untuk kegunaan aplikasi.\n\nNyatakan parameter format untuk mengubah format outputnya. Untuk melihat representasi format $1 yang bukan HTML, tetapkan format=$2.\n\nSila rujuk [https://www.mediawiki.org/wiki/API dokumentasi lengkapnya] ataupun [[Special:ApiHelp/main|bantuan API]] untuk keterangan lanjut.",
+ "api-help-title": "Bantuan API MediaWiki",
+ "api-help-lead": "Ini merupakan laman dokumentasi MediaWiki API yang dihasilkan secara automatik.\n\nDokumentasi dan contoh-contoh: https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "Modul utama",
+ "api-help-flag-deprecated": "Modul ini sudah lapuk.",
+ "api-help-flag-internal": "<strong>Modul ini dalaman atau tidak stabil.</strong> Operasinya boleh berubah tanpa amaran.",
+ "api-help-flag-readrights": "Modul ini memerlukan hak membaca.",
+ "api-help-flag-writerights": "Modul ini memerlukan hak menulis.",
+ "api-help-flag-mustbeposted": "Modul ini menerima permohonan POST sahaja.",
+ "api-help-flag-generator": "Modul ini boleh digunakan sebagai penjana.",
+ "api-help-parameters": "{{PLURAL:$1|Parameter}}:",
+ "api-help-param-deprecated": "Lapuk.",
+ "api-help-param-required": "Parameter ini diwajibkan.",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Mestilah kosong|Bolehlah kosong atau $2}}",
+ "api-help-param-limit2": "Dibenarkannya tidak lebih daripada $1 ($2 untuk bot).",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=Nilainya|2=Nilai-nilainya}} mesti tidak melebihi $3.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=Nilainya|2=Nilai-nilainya}} mestilah antara $2 hingga $3.",
+ "api-help-param-multi-separate": "Asingkan nilai-nilai dengan \"|\".",
+ "api-help-param-multi-max": "Bilangan nilai maksimum adalah {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} untuk bot).",
+ "api-help-param-default": "Asal: $1",
+ "api-help-param-default-empty": "Asal: <span class=\"apihelp-empty\">(kosong)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(tiada keterangan)</span>",
+ "api-help-examples": "{{PLURAL:$1|Contoh|Contoh-contoh}}:",
+ "api-help-permissions": "{{PLURAL:$1|Keizinan}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Diberikan kepada}}: $2",
+ "api-credits-header": "Kredit"
+}
diff --git a/includes/api/i18n/nap.json b/includes/api/i18n/nap.json
new file mode 100644
index 00000000..a285b04a
--- /dev/null
+++ b/includes/api/i18n/nap.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chelin"
+ ]
+ },
+ "apihelp-block-description": "Blocca n'utente.",
+ "apihelp-createaccount-param-name": "Nomme utente.",
+ "apihelp-delete-description": "Scancella 'na paggena."
+}
diff --git a/includes/api/i18n/nb.json b/includes/api/i18n/nb.json
new file mode 100644
index 00000000..6dcba40a
--- /dev/null
+++ b/includes/api/i18n/nb.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jeblad"
+ ]
+ },
+ "apihelp-main-param-action": "Hvilken handling skal utføres",
+ "apihelp-main-param-format": "Resultatets format.",
+ "apihelp-main-param-servedby": "Inkluder navnet på tjeneren som utførte forespørselen i resultatene.",
+ "apihelp-main-param-curtimestamp": "Inkluder det nåværende tidsmerket i resultatet.",
+ "apihelp-dbg-description": "Resultatdata i PHP's var_export() format.",
+ "apihelp-dbgfm-description": "Resultatdata i PHP's var_export() format (pen utskrift i HTML).",
+ "apihelp-dump-description": "Resultatdata i PHP's var_export() format.",
+ "apihelp-dumpfm-description": "Resultatdata i PHP's var_export() format (pen utskrift i HTML).",
+ "apihelp-json-description": "Resultatdata i JSON-format.",
+ "apihelp-none-description": "Ingen resultat.",
+ "api-help-flag-readrights": "Denne modulen krever lesetilgang.",
+ "api-help-flag-writerights": "Denne modulen krever skrivetilgang.",
+ "api-help-flag-mustbeposted": "Denne modulen aksepterer bare POST forespørsler.",
+ "api-help-flag-generator": "Denne modulen kan brukes som en generator.",
+ "api-help-parameters": "{{PLURAL:$1|Parameter|Parametre}}:",
+ "api-help-param-deprecated": "Utgått.",
+ "api-help-param-required": "Denne parameteren er påkrevd."
+}
diff --git a/includes/api/i18n/nds.json b/includes/api/i18n/nds.json
new file mode 100644
index 00000000..3f7cb12e
--- /dev/null
+++ b/includes/api/i18n/nds.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Servien"
+ ]
+ },
+ "apihelp-login-param-password": "Passwoort."
+}
diff --git a/includes/api/i18n/nl.json b/includes/api/i18n/nl.json
new file mode 100644
index 00000000..72816b99
--- /dev/null
+++ b/includes/api/i18n/nl.json
@@ -0,0 +1,61 @@
+{
+ "@metadata": {
+ "authors": [
+ "Siebrand",
+ "Sjoerddebruin",
+ "Robin0van0der0vliet",
+ "Mar(c)",
+ "Valhallasw",
+ "Sikjes",
+ "Macofe",
+ "SPQRobin"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentatie]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-maillijst]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-aankondigingen]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bugs & verzoeken]\n</div>\n<strong>Status:</strong> Alle functies die op deze pagina worden weergegeven horen te werken. Aan de API wordt actief gewerkt, en deze kan gewijzigd worden. Abonneer u op de [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ e-maillijst mediawiki-api-announce] voor meldingen over aanpassingen.\n\n<strong>Foutieve verzoeken:</strong> als de API foutieve verzoeken ontvangt, wordt er geantwoord met een HTTP-header met de sleutel \"MediaWiki-API-Error\" en daarna worden de waarde van de header en de foutcode op dezelfde waarde ingesteld. Zie [[mw:API:Errors_and_warnings|API: Errors and warnings]] voor meer informatie.",
+ "apihelp-main-param-action": "Welke handeling uit te voeren.",
+ "apihelp-main-param-format": "De opmaak van de uitvoer.",
+ "apihelp-main-param-maxlag": "De maximale vertraging kan gebruikt worden als MediaWiki is geïnstalleerd op een databasecluster die gebruik maakt van replicatie. Om te voorkomen dat handelingen nog meer databasereplicatievertraging veroorzaken, kan deze parameter er voor zorgen dat de client wacht totdat de replicatievertraging lager is dan de aangegeven waarde. In het geval van buitensporige vertraging, wordt de foutcode <samp>maxlag</samp> teruggegeven met een bericht als <samp>Waiting for $host: $lag seconds lagged</samp>.<br />Zie [[mw:Manual:Maxlag_parameter|Handboek: Maxlag parameter]] voor mee informatie.",
+ "apihelp-main-param-smaxage": "Stelt de header \"<code>s-maxage</code>\" in op het aangegeven aantal seconden. Foutmeldingen komen nooit in de cache.",
+ "apihelp-main-param-maxage": "Stelt de header \"<code>max-age</code>\" in op het aangegeven aantal seconden. Foutmeldingen komen nooit in de cache.",
+ "apihelp-main-param-assert": "Controleer of de gebruiker is aangemeld als <kbd>user</kbd> is meegegeven, en of de gebruiker het robotgebruikersrecht heeft als <kbd>bot</kbd> is meegegeven.",
+ "apihelp-main-param-requestid": "Elke waarde die hier gegeven wordt, wordt aan het antwoord toegevoegd. Dit kan gebruikt worden om verzoeken te onderscheiden.",
+ "apihelp-main-param-servedby": "Voeg de hostnaam van de server die de aanvraag heeft afgehandeld toe aan het antwoord.",
+ "apihelp-main-param-curtimestamp": "Huidige tijd aan het antwoord toevoegen.",
+ "apihelp-block-description": "Gebruiker blokkeren.",
+ "apihelp-block-param-reason": "Reden voor blokkade.",
+ "apihelp-block-param-autoblock": "Blokkeer automatisch het laatst gebruikte IP-adres en ieder volgend IP-adres van waaruit ze proberen in te loggen.",
+ "apihelp-block-param-reblock": "De huidige blokkade aanpassen als de gebruiker al geblokkeerd is.",
+ "apihelp-createaccount-param-name": "Gebruikersnaam.",
+ "apihelp-delete-description": "Verwijder een pagina.",
+ "apihelp-delete-example-simple": "Verwijder <kbd>Hoofdpagina</kbd>.",
+ "apihelp-delete-example-reason": "Verwijder <kbd>Hoofdpagina</kbd> met als reden <kbd>Voorbereiding voor verplaatsing</kbd>.",
+ "apihelp-disabled-description": "Deze module is uitgeschakeld.",
+ "apihelp-edit-param-minor": "Kleine bewerking.",
+ "apihelp-edit-param-notminor": "Geen kleine bewerking.",
+ "apihelp-edit-param-bot": "Markeer deze bewerking als bot.",
+ "apihelp-edit-param-createonly": "Bewerk de pagina niet als die al bestaat.",
+ "apihelp-edit-param-nocreate": "Geef een foutmelding als de pagina niet bestaat.",
+ "apihelp-edit-param-watch": "Voeg de pagina toe aan je volglijst.",
+ "apihelp-edit-param-unwatch": "Verwijder de pagina van je volglijst.",
+ "apihelp-edit-example-edit": "Pagina bewerken",
+ "apihelp-emailuser-description": "Gebruiker e-mailen.",
+ "apihelp-emailuser-param-target": "Gebruiker naar wie de e-mail moet worden gestuurd.",
+ "apihelp-emailuser-param-subject": "Onderwerpkoptekst.",
+ "apihelp-emailuser-param-text": "E-mailtekst.",
+ "apihelp-emailuser-param-ccme": "Stuur mij een kopie van deze e-mail.",
+ "apihelp-expandtemplates-param-title": "Paginanaam.",
+ "apihelp-feedcontributions-param-year": "Van jaar (en eerder).",
+ "apihelp-feedcontributions-param-month": "Van maand (en eerder).",
+ "apihelp-login-param-name": "Gebruikersnaam.",
+ "apihelp-login-param-password": "Wachtwoord.",
+ "apihelp-login-param-domain": "Domein (optioneel).",
+ "apihelp-login-example-login": "Aanmelden",
+ "apihelp-move-description": "Pagina hernoemen.",
+ "api-help-flag-readrights": "Voor deze module zijn leesrechten nodig.",
+ "api-help-flag-writerights": "Voor deze module zijn schrijfrechten nodig.",
+ "api-help-parameters": "{{PLURAL:$1|Parameter|Parameters}}:",
+ "api-help-param-deprecated": "Verouderd.",
+ "api-help-param-default": "Standaard: $1",
+ "api-credits-header": "Vermeldingen",
+ "api-credits": "API-ontwikkelaars:\n* Roan Kattouw (hoofdontwikkelaar september 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (oorspronkelijke ontwikkelaar, hoofdontwikkelaar september 2006 – september 2007)\n* Brad Jorsch (hoofdontwikkelaar 2013 – heden)\n\nStuur uw opmerkingen, suggesties en vragen naar mediawiki-api@lists.wikimedia.org\nof maak een melding aan op https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/oc.json b/includes/api/i18n/oc.json
new file mode 100644
index 00000000..dc12b663
--- /dev/null
+++ b/includes/api/i18n/oc.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cedric31"
+ ]
+ },
+ "apihelp-main-param-action": "Quina accion cal efectuar.",
+ "apihelp-main-param-format": "Lo format de sortida.",
+ "apihelp-block-description": "Blocar un utilizaire.",
+ "apihelp-block-param-reason": "Motiu del blocatge.",
+ "apihelp-block-param-nocreate": "Empachar la creacion de compte.",
+ "apihelp-checktoken-param-token": "Geton de testar.",
+ "apihelp-createaccount-param-name": "Nom d'utilizaire.",
+ "apihelp-delete-example-simple": "Suprimir la <kbd>Pagina principala</kbd>.",
+ "apihelp-edit-param-text": "Contengut de la pagina.",
+ "apihelp-edit-param-minor": "Modificacion menora."
+}
diff --git a/includes/api/i18n/pa.json b/includes/api/i18n/pa.json
new file mode 100644
index 00000000..96c86941
--- /dev/null
+++ b/includes/api/i18n/pa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Babanwalia"
+ ]
+ },
+ "apihelp-help-example-recursive": "ਇੱਕੋ ਸਫ਼ੇ 'ਤੇ ਸਾਰੀ ਮਦਦ"
+}
diff --git a/includes/api/i18n/pam.json b/includes/api/i18n/pam.json
new file mode 100644
index 00000000..c82d88fc
--- /dev/null
+++ b/includes/api/i18n/pam.json
@@ -0,0 +1,31 @@
+{
+ "@metadata": {
+ "authors": [
+ "Leeheonjin"
+ ]
+ },
+ "apihelp-delete-example-simple": "Buran ya ing <kbd>Pun Bulung</kbd>.",
+ "apihelp-edit-example-edit": "Alilan ya ing bulung.",
+ "apihelp-feedrecentchanges-example-simple": "Pakit deng bayung mengayalili.",
+ "apihelp-help-example-main": "Saup para king pun modyul.",
+ "apihelp-help-example-recursive": "Deng eganaganang saup king metung a bulung.",
+ "apihelp-login-example-login": "Magpatala (login)",
+ "apihelp-patrol-example-rcid": "Magbante king bayung mengayalili.",
+ "apihelp-patrol-example-revid": "Banten ing meyalili.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "E malyaring gamitan a yating <var>$3user</var>.",
+ "apihelp-query+allpages-example-B": "Ipakit ing talaan da reng bulung a mangumpisa king titik <kbd>B</kbd>.",
+ "apihelp-query+categoryinfo-example-simple": "Kumuwa ning impormasyun tungkul king <kbd>Kategorya:Foo</kbd> at <kbd>Kategorya:Bar</kbd>.",
+ "apihelp-query+deletedrevs-example-mode2": "Ilista la reng 50 binurang kontribusyun nang <kbd>Bob</kbd> (mode 2).",
+ "apihelp-query+deletedrevs-example-mode3-talk": "Ilista mu la reng minunang 50 meburang bulung king {{ns:talk}} lagyu-espasyu (mode 3)",
+ "apihelp-query+duplicatefiles-example-generated": "Mayintun para kareng duplika da reng egana-ganang simpan (file).",
+ "apihelp-query+extlinks-example-simple": "Kumuwa ning lista da reng suglung paluwal king <kbd>Pun Bulung</kbd>.",
+ "apihelp-query+exturlusage-example-simple": "Pakit la reng bulung a makasuglung king <kbd>http://www.mediawiki.org</kbd>.",
+ "apihelp-query+imageusage-example-simple": "Ipakit la reng bulung a gagamit ning [[:Simpan:Albert Einstein Head.jpg]].",
+ "apihelp-query+langbacklinks-example-simple": "Kunan deng bulung a maka-suglung king [[:fr:Test]].",
+ "apihelp-query+protectedtitles-example-generator": "Pantunan deng suglung king maka-protektang titulu king pun lagyu-espasyu.",
+ "apihelp-query+recentchanges-example-simple": "Talaan da reng bayung mengayalili.",
+ "apihelp-query+search-example-text": "Pantunan mo la reng tekstu para king <kbd>kabaldugan</kbd>",
+ "apihelp-query+siteinfo-example-simple": "Kung ing impormasyun ning sityu.",
+ "apihelp-upload-example-url": "Maglulan (upload) ibat king URL.",
+ "apihelp-watch-example-unwatch": "E banten ing bulung <kbd>Pun Bulung</kbd>"
+}
diff --git a/includes/api/i18n/pl.json b/includes/api/i18n/pl.json
new file mode 100644
index 00000000..1a5c8975
--- /dev/null
+++ b/includes/api/i18n/pl.json
@@ -0,0 +1,117 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chrumps",
+ "Py64",
+ "Pan Cube",
+ "Alan ffm",
+ "Devwebtel",
+ "Macofe",
+ "Pio387",
+ "Peter Bowman",
+ "Darellur"
+ ]
+ },
+ "apihelp-main-param-action": "Wybierz akcję do wykonania.",
+ "apihelp-main-param-format": "Format danych wyjściowych.",
+ "apihelp-main-param-maxlag": "Maksymalne opóźnienie mogą być używane kiedy MediaWiki jest zainstalowana w klastrze zreplikowanej bazy danych. By zapisać działania powodujące większe opóźnienie replikacji, ten parametr może wymusić czekanie u klienta, dopóki opóźnienie replikacji jest mniejsze niż określona wartość. W przypadku nadmiernego opóźnienia, kod błędu <samp>maxlag</samp> jest zwracany z wiadomością jak <samp>Oczekiwanie na $host: $lag sekund opóźnienia</samp>.<br />Zobacz [[mw:Manual:Maxlag_parameter|Podręcznik:Parametr Maxlag]] by uzyskać więcej informacji.",
+ "apihelp-main-param-assert": "Sprawdź, czy użytkownik jest zalogowany jeżeli jest ustawiony na <kbd>użytkownik</kbd>, lub ma prawa bota jeśli <kbd>bot</kbd>.",
+ "apihelp-block-description": "Zablokuj użytkownika.",
+ "apihelp-block-param-user": "Nazwa użytkownika, adres IP lub zakres adresów IP, które chcesz zablokować.",
+ "apihelp-block-param-reason": "Powód blokady.",
+ "apihelp-block-param-nocreate": "Zapobiegnij utworzeniu konta.",
+ "apihelp-block-param-watchuser": "Obserwuj stronę użytkownika i jego IP oraz jego stronę dyskusji.",
+ "apihelp-block-example-ip-simple": "Zablokuj IP <kbd>192.0.2.5</kbd> na 3 dni za <kbd>Pierwszy atak</kbd>.",
+ "apihelp-createaccount-param-name": "Nazwa użytkownika",
+ "apihelp-delete-description": "Usuń stronę.",
+ "apihelp-delete-param-watch": "Dodaj stronę do twojej listy obserwowanych.",
+ "apihelp-delete-param-unwatch": "Usuń stronę z twojej listy obserwowanych.",
+ "apihelp-delete-example-simple": "Usuń stronę główną",
+ "apihelp-disabled-description": "Ten moduł został wyłączony.",
+ "apihelp-edit-description": "Utwórz i edytuj strony.;",
+ "apihelp-edit-param-text": "Zawartość strony.",
+ "apihelp-edit-param-minor": "Drobna zmiana.",
+ "apihelp-edit-param-notminor": "Nie drobna zmiana.",
+ "apihelp-edit-param-bot": "Oznacz tę edycję jako edycję bota.",
+ "apihelp-edit-param-watch": "Dodaj stronę do aktualnej listy obserwacji użytkownika.",
+ "apihelp-edit-param-unwatch": "Usuń stronę z aktualnej listy obserwacji użytkownika.",
+ "apihelp-edit-example-edit": "Edytuj stronę",
+ "apihelp-emailuser-description": "Wyślij e‐mail do użytkownika.",
+ "apihelp-feedrecentchanges-example-simple": "Pokaż ostatnie zmiany.",
+ "apihelp-help-description": "Wyświetl pomoc dla określonych modułów.",
+ "apihelp-help-param-modules": "Moduły do wyświetlenia pomocy dla (wartości <var>action</var> i <var>format</var> parametry, lub <kbd>main</kbd>). Może określić podmoduły z <kbd>+</kbd>.",
+ "apihelp-help-param-recursivesubmodules": "Zawiera pomoc dla podmodułów rekursywnie.",
+ "apihelp-help-example-main": "Pomoc dla modułu głównego",
+ "apihelp-help-example-recursive": "Cała pomoc na jednej stronie.",
+ "apihelp-help-example-help": "Pomoc dla modułu pomocy",
+ "apihelp-login-param-name": "Nazwa użytkownika.",
+ "apihelp-login-param-password": "Hasło.",
+ "apihelp-login-example-login": "Zaloguj się",
+ "apihelp-managetags-param-ignorewarnings": "Czy zignorować ostrzeżenia, które pojawiają się w trakcie operacji.",
+ "apihelp-move-description": "Przenieś stronę.",
+ "apihelp-move-param-reason": "Powód zmiany nazwy.",
+ "apihelp-move-param-ignorewarnings": "Ignoruj wszystkie ostrzeżenia.",
+ "apihelp-protect-example-protect": "Zabezpiecz stronę",
+ "apihelp-query+allpages-example-B": "Pokaż listę stron rozpoczynających się na literę <kbd>B</kbd>.",
+ "apihelp-query+filearchive-example-simple": "Pokaż listę wszystkich usuniętych plików.",
+ "apihelp-query+imageinfo-paramvalue-prop-canonicaltitle": "Dodaje kanoniczny tytuł pliku.",
+ "apihelp-query+imageinfo-paramvalue-prop-dimensions": "Alias rozmiaru.",
+ "apihelp-query+imageinfo-paramvalue-prop-mime": "Dodaje typ MIME pliku.",
+ "apihelp-query+info-paramvalue-prop-watchers": "Liczba obserwujących, jeśli jest to dozwolone.",
+ "apihelp-query+info-paramvalue-prop-readable": "Czy użytkownik może przeczytać tę stronę.",
+ "apihelp-query+prefixsearch-param-offset": "Liczba wyników do pominięcia.",
+ "apihelp-query+recentchanges-example-simple": "Lista ostatnich zmian.",
+ "apihelp-query+search-description": "Wykonaj wyszukiwanie pełnotekstowe.",
+ "apihelp-query+watchlist-param-excludeuser": "Nie wyświetlaj zmian wykonanych przez tego użytkownika.",
+ "apihelp-unblock-param-reason": "Powód odblokowania.",
+ "apihelp-undelete-param-reason": "Powód przywracania.",
+ "apihelp-upload-param-filename": "Nazwa pliku docelowego.",
+ "apihelp-userrights-param-user": "Nazwa użytkownika.",
+ "apihelp-userrights-param-reason": "Powód zmiany.",
+ "apihelp-dbg-description": "Dane wyjściowe w formacie <code>var_export()</code> (funkcji PHP).",
+ "apihelp-dbgfm-description": "Dane wyjściowe w formacie <code>var_export()</code> (funkcji PHP) (prawidłowo wyświetlane w HTML).",
+ "apihelp-dump-description": "Dane wyjściowe w formacie <code>var_dump()</code> (funkcji PHP).",
+ "apihelp-dumpfm-description": "Dane wyjściowe w formacie <code>var_dump()</code> (funkcji PHP) (prawidłowo wyświetlane w HTML).",
+ "apihelp-json-description": "Dane wyjściowe w formacie JSON.",
+ "apihelp-jsonfm-description": "Dane wyjściowe w formacie JSON (prawidłowo wyświetlane w HTML).",
+ "apihelp-php-description": "Dane wyjściowe w serializowany formacie PHP.",
+ "apihelp-phpfm-description": "Dane wyjściowe w serializowanym formacie PHP (prawidłowo wyświetlane w HTML).",
+ "apihelp-txt-description": "Dane wyjściowe w formacie <code>print_r()</code> (funkcji PHP).",
+ "apihelp-txtfm-description": "Dane wyjściowe w formacie <code>print_r()</code> (funkcji PHP) (prawidłowo wyświetlane w HTML).",
+ "apihelp-wddx-description": "Dane wyjściowe w formacie WDDX.",
+ "apihelp-wddxfm-description": "Dane wyjściowe w formacie WDDX (prawidłowo wyświetlane w HTML).",
+ "apihelp-xml-description": "Dane wyjściowe w formacie XML.",
+ "apihelp-xml-param-xslt": "Jeśli określony, dodaje &lt;xslt&gt; jako arkusz styli. Powinna to być strona wiki w przestrzeni nazw MediaWiki, której nazwy stron kończą się na \".xsl\".",
+ "apihelp-xmlfm-description": "Dane wyjściowe w formacie XML (prawidłowo wyświetlane w HTML).",
+ "apihelp-yaml-description": "Dane wyjściowe w formacie YAML.",
+ "apihelp-yamlfm-description": "Dane wyjściowe w formacie YAML (prawidłowo wyświetlane w HTML).",
+ "api-format-title": "Wynik MediaWiki API",
+ "api-help-title": "Pomoc MediaWiki API",
+ "api-help-lead": "To jest automatycznie wygenerowana strona dokumentacji MediaWiki API.\nDokumentacja i przykłady: https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "Moduł główny",
+ "api-help-flag-deprecated": "Ten moduł jest przestarzały.",
+ "api-help-flag-internal": "<strong>Ten moduł jest wewnętrzny lub niestabilny.</strong> Jego działanie może się zmienić bez uprzedzenia.",
+ "api-help-flag-readrights": "Ten moduł wymaga praw odczytu.",
+ "api-help-flag-writerights": "Ten moduł wymaga praw zapisu.",
+ "api-help-flag-mustbeposted": "Ten moduł akceptuje tylko żądania POST.",
+ "api-help-flag-generator": "Ten moduł może być użyty jako generator.",
+ "api-help-parameters": "{{PLURAL:$1|Parametr|Parametry}}:",
+ "api-help-param-deprecated": "Przestarzałe.",
+ "api-help-param-required": "Ten parametr jest wymagany.",
+ "api-help-param-list": "{{PLURAL:$1|1=Jedna wartość|2=Wartości (oddziel za pomocą <kbd>{{!}}</kbd>)}}: $2",
+ "api-help-param-limit": "Nie więcej niż $1 dozwolone.",
+ "api-help-param-limit2": "Nie więcej niż $1 ($2 dla botów) dozwolone.",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=Wartość|2=Wartości}} musza być mniejsze niż $2.",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=Wartość musi|2=Wartości muszą}} być nie większa niż $3.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=Wartość musi|2=Wartości muszą}} być pomiędzy $2 a $3.",
+ "api-help-param-multi-separate": "Oddziel wartości za pomocą <kbd>|</kbd>.",
+ "api-help-param-multi-max": "Maksymalna ilość wartości to {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} dla botów).",
+ "api-help-param-default": "Domyślnie: $1",
+ "api-help-param-default-empty": "Domyślnie: <span class=\"apihelp-empty\">(puste)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(bez opisu)</span>",
+ "api-help-examples": "{{PLURAL:$1|Przykład|Przykłady}}:",
+ "api-help-permissions": "{{PLURAL:$2|Uprawnienie|Uprawnienia}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Przydzielone dla}}: $2",
+ "api-credits-header": "Twórcy",
+ "api-credits": "Deweloperzy API:\n* Roan Kattouw (główny programista wrzesień 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (twórca, główny programista wrzesień 2006–wrzesień 2007)\n* Brad Jorsch (główny programista 2013–obecnie)\n\nProsimy wysyłać komentarze, sugestie i pytania do mediawiki-api@lists.wikimedia.org\nlub zgłoś błąd na https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/ps.json b/includes/api/i18n/ps.json
new file mode 100644
index 00000000..b9399024
--- /dev/null
+++ b/includes/api/i18n/ps.json
@@ -0,0 +1,29 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ahmed-Najib-Biabani-Ibrahimkhel"
+ ]
+ },
+ "apihelp-block-description": "په يو کارن بنديز لگول.",
+ "apihelp-block-param-user": "کارن-نوم، IP پته، يا IP سيمې باندې بنديز لگول.",
+ "apihelp-block-param-reason": "د بنديز سبب.",
+ "apihelp-createaccount-param-name": "کارن-نوم.",
+ "apihelp-delete-description": "يو مخ ړنگول.",
+ "apihelp-edit-param-text": "مخ مېنځپانگه.",
+ "apihelp-edit-example-edit": "يو مخ سمول.",
+ "apihelp-emailuser-description": "کارن ته برېښليک لېږل.",
+ "apihelp-expandtemplates-param-title": "د مخ سرليک.",
+ "apihelp-login-param-name": "کارن نوم.",
+ "apihelp-login-param-password": "پټنوم.",
+ "apihelp-login-param-domain": "شپول (اختياري).",
+ "apihelp-login-example-login": "ننوتل.",
+ "apihelp-move-description": "يو مخ لېږدول.",
+ "apihelp-query+search-example-simple": "د <kbd>مانا</kbd> پلټل.",
+ "apihelp-query+search-example-text": "د <kbd>مانا</kbd> لپاره متنونه پلټل.",
+ "apihelp-userrights-param-user": "کارن نوم.",
+ "apihelp-userrights-param-userid": "کارن پېژند.",
+ "api-help-param-default": "تلواليز: $1",
+ "api-help-param-default-empty": "تلواليز: <span class=\"apihelp-empty\">(تش)</span>",
+ "api-help-examples": "{{PLURAL:$1|بېلگه|بېلگې}}:",
+ "api-help-permissions": "{{PLURAL:$1|پرېښه|پرېښې}}:"
+}
diff --git a/includes/api/i18n/pt-br.json b/includes/api/i18n/pt-br.json
new file mode 100644
index 00000000..5af806d0
--- /dev/null
+++ b/includes/api/i18n/pt-br.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Fasouzafreitas"
+ ]
+ },
+ "apihelp-main-param-requestid": "Qualquer valor dado aqui será incluído na resposta. Pode ser usado para distinguir requisições.",
+ "apihelp-block-description": "Bloquear um usuário",
+ "apihelp-block-param-user": "Nome de usuário, endereço IP ou faixa de IP para bloquear.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Ocultar alterações feitas pelo usuário atual.",
+ "apihelp-feedrecentchanges-example-30days": "Mostrar as alterações recentes por 30 dias.",
+ "apihelp-move-param-movetalk": "Renomear a página de discussão, se existir.",
+ "apihelp-options-example-reset": "Resetar todas as preferências"
+}
diff --git a/includes/api/i18n/pt.json b/includes/api/i18n/pt.json
new file mode 100644
index 00000000..72044db9
--- /dev/null
+++ b/includes/api/i18n/pt.json
@@ -0,0 +1,104 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vitorvicentevalente",
+ "Fúlvio",
+ "Macofe"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentação]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de discussão]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Anúncios da API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Erros e solicitações]\n</div>\n<strong>Estado:</strong> Todas as funcionalidades mostradas nesta página deveriam estar a funcionar, mas a API ainda está em activo desenvolvimento, e pode ser alterada a qualquer momento. Inscreva-se na [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ lista de discussão mediawiki-api-announce] para ser informado acerca das actualizações.\n\n<strong>Solicitações erradas:</strong> Quando solicitações erradas são enviadas à API, um cabeçalho em HTTP será enviado com a chave \"MediaWiki-API-Error\" e, em seguida, tanto o valor do cabeçalho quanto o código de erro retornado serão definidos com o mesmo valor. Para mais informação, consulte [[mw:API:Errors_and_warnings|API: Errors and warnings]].",
+ "apihelp-main-param-action": "Qual acção a executar.",
+ "apihelp-main-param-format": "O formato de saída.",
+ "apihelp-block-description": "Bloquear um utilizador.",
+ "apihelp-block-param-user": "Nome de utilizador(a), endereço ou gama de IP que pretende bloquear.",
+ "apihelp-block-param-reason": "Motivo do bloqueio.",
+ "apihelp-block-param-nocreate": "Impedir criação de contas.",
+ "apihelp-createaccount-description": "Criar uma nova conta.",
+ "apihelp-createaccount-param-name": "Nome de utilizador(a).",
+ "apihelp-createaccount-param-email": "Endereço de correio eletrónico do utilizador (opcional).",
+ "apihelp-createaccount-param-realname": "Nome verdadeiro do utilizador (opcional).",
+ "apihelp-delete-description": "Eliminar uma página.",
+ "apihelp-delete-param-watch": "Adicionar esta página à lista de vigiadas.",
+ "apihelp-delete-param-unwatch": "Remover esta página da lista de vigiadas.",
+ "apihelp-delete-example-simple": "Eliminar <kbd>Página Principal</kbd>.",
+ "apihelp-disabled-description": "O módulo foi desativado.",
+ "apihelp-edit-description": "Criar e editar páginas.",
+ "apihelp-edit-param-sectiontitle": "Título para uma nova seção.",
+ "apihelp-edit-param-text": "Conteúdo da página.",
+ "apihelp-edit-param-minor": "Edição menor.",
+ "apihelp-edit-param-bot": "Marcar esta edição como robô.",
+ "apihelp-edit-example-edit": "Editar uma página",
+ "apihelp-emailuser-description": "Enviar correio eletrónico a utilizador.",
+ "apihelp-emailuser-param-subject": "Assunto.",
+ "apihelp-emailuser-param-text": "Texto.",
+ "apihelp-expandtemplates-param-title": "Título da página.",
+ "apihelp-feedcontributions-param-feedformat": "O formato do feed.",
+ "apihelp-feedcontributions-param-deletedonly": "Mostrar apenas contribuições eliminadas.",
+ "apihelp-feedcontributions-param-showsizediff": "Mostrar diferença de tamanho entre edições.",
+ "apihelp-feedrecentchanges-param-feedformat": "O formato do feed.",
+ "apihelp-feedrecentchanges-param-limit": "Número máximo de resultados a apresentar.",
+ "apihelp-feedrecentchanges-param-from": "Mostrar alterações desde então.",
+ "apihelp-feedrecentchanges-param-hideminor": "Ocultar edições menores.",
+ "apihelp-feedrecentchanges-param-hidebots": "Ocultar alterações feitas por robôs.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Ocultar alterações patrulhadas.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Ocultar alterações feitas por mim.",
+ "apihelp-feedrecentchanges-param-target": "Mostrar apenas mudanças em páginas afluentes a esta.",
+ "apihelp-feedrecentchanges-example-simple": "Mostrar mudanças recentes",
+ "apihelp-help-example-main": "Ajuda para o módulo principal",
+ "apihelp-help-example-recursive": "Toda a ajuda numa página",
+ "apihelp-login-param-name": "Nome de utilizador(a).",
+ "apihelp-login-param-password": "Palavra-passe.",
+ "apihelp-login-param-domain": "Domínio (opcional).",
+ "apihelp-login-example-login": "Entrar",
+ "apihelp-logout-description": "Terminar e limpar dados de sessão.",
+ "apihelp-managetags-description": "Executar tarefas de gestão relacionadas com alteração de etiquetas.",
+ "apihelp-managetags-param-reason": "Um motivo, opcional, para a criação, eliminação, ativação ou desativação da etiqueta.",
+ "apihelp-move-description": "Mover uma página.",
+ "apihelp-move-param-noredirect": "Não criar um redirecionamento.",
+ "apihelp-move-param-ignorewarnings": "Ignorar quaisquer avisos.",
+ "apihelp-opensearch-param-limit": "Número máximo de resultados a apresentar.",
+ "apihelp-options-param-reset": "Reiniciar preferências para os padrões do sítio.",
+ "apihelp-options-example-reset": "Reiniciar todas as preferências",
+ "apihelp-patrol-description": "Patrulhar uma página ou edição.",
+ "apihelp-patrol-example-rcid": "Patrulhar uma mudança recente",
+ "apihelp-patrol-example-revid": "Patrulhar uma edição",
+ "apihelp-protect-example-protect": "Proteger uma página",
+ "apihelp-query+allcategories-description": "Enumerar todas as categorias.",
+ "apihelp-query+allpages-param-prefix": "Pesquisa para todos os títulos de páginas que comecem com este valor.",
+ "apihelp-query+allpages-example-generator": "Mostrar informação sobre 4 páginas que comecem com a letra <kbd>T</kbd>.",
+ "apihelp-query+allusers-example-Y": "Lista de utilizadores que comecem com <kbd>Y</kbd>.",
+ "apihelp-query+blocks-param-limit": "O número máximo de bloqueios a listar.",
+ "apihelp-query+categorymembers-description": "Lista de todas as páginas numa categoria fornecida.",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|Modo|Modos}}: $2",
+ "apihelp-query+deletedrevs-param-excludeuser": "Não listar edições deste utilizador.",
+ "apihelp-query+deletedrevs-param-namespace": "Listar apenas as páginas neste domínio.",
+ "apihelp-query+filearchive-example-simple": "Mostrar lista de todos os ficheiros eliminados",
+ "apihelp-query+info-description": "Obter informação básica da página.",
+ "apihelp-query+recentchanges-example-simple": "Lista de mudanças recentes",
+ "apihelp-unblock-description": "Desbloquear um utilizador.",
+ "apihelp-unblock-param-reason": "Motivo para o desbloqueio.",
+ "apihelp-undelete-param-title": "Título da página a restaurar.",
+ "apihelp-upload-param-watch": "Vigiar página.",
+ "apihelp-upload-param-ignorewarnings": "Ignorar todos os avisos.",
+ "apihelp-userrights-param-user": "Nome de utilizador(a).",
+ "apihelp-userrights-param-userid": "ID de utilizador.",
+ "apihelp-userrights-param-add": "Adicionar o utilizador a estes grupos.",
+ "apihelp-userrights-param-remove": "Remover este utilizador destes grupos.",
+ "apihelp-watch-example-unwatch": "Deixar de vigiar a página <kbd>Página Principal</kbd>.",
+ "apihelp-json-description": "Dados de saída em formato JSON.",
+ "api-help-title": "Ajuda API da MediaWiki",
+ "api-help-main-header": "Módulo principal",
+ "api-help-flag-deprecated": "Este módulo está obsoleto.",
+ "api-help-parameters": "{{PLURAL:$1|Parâmetro|Parâmetros}}:",
+ "api-help-param-deprecated": "Obsoleto.",
+ "api-help-param-required": "Este parâmetro é obrigatório.",
+ "api-help-param-multi-separate": "Separe os valores com <kbd>|</kbd>.",
+ "api-help-param-default": "Padrão: $1",
+ "api-help-param-default-empty": "Padrão: <span class=\"apihelp-empty\">(vazio)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(sem descrição)</span>",
+ "api-help-examples": "{{PLURAL:$1|Exemplo|Exemplos}}:",
+ "api-help-permissions": "{{PLURAL:$1|Permissão|Permissiões}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|Concedida a|Concedidas a}}: $2",
+ "api-credits-header": "Créditos",
+ "api-credits": "Programadores API:\n* Roan Kattouw (programador principal Set 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (criador, programador-líder Set 2006–Set 2007)\n* Brad Jorsch (programador-líder 2013–presente)\n\nPor favor, envie os seus comentários, sugestões e perguntas para mediawiki-api@lists.wikimedia.org ou reporte um erro técnico em https://phabricator.wikimedia.org/."
+}
diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json
new file mode 100644
index 00000000..9bf98247
--- /dev/null
+++ b/includes/api/i18n/qqq.json
@@ -0,0 +1,1069 @@
+{
+ "@metadata": {
+ "authors": [
+ "Liuxinyu970226",
+ "Robby",
+ "Shirayuki",
+ "Umherirrender",
+ "McDutchie",
+ "Raymond",
+ "Anomie"
+ ]
+ },
+ "apihelp-main-description": "{{doc-apihelp-description|main}}",
+ "apihelp-main-param-action": "{{doc-apihelp-param|main|action}}",
+ "apihelp-main-param-format": "{{doc-apihelp-param|main|format}}",
+ "apihelp-main-param-maxlag": "{{doc-apihelp-param|main|maxlag}}\n\n\"$host\" and \"$lag\" are not variables and appear as is.",
+ "apihelp-main-param-smaxage": "{{doc-apihelp-param|main|smaxage}}",
+ "apihelp-main-param-maxage": "{{doc-apihelp-param|main|maxage}}",
+ "apihelp-main-param-assert": "{{doc-apihelp-param|main|assert}}",
+ "apihelp-main-param-requestid": "{{doc-apihelp-param|main|requestid}}",
+ "apihelp-main-param-servedby": "{{doc-apihelp-param|main|servedby}}",
+ "apihelp-main-param-curtimestamp": "{{doc-apihelp-param|main|curtimestamp}}",
+ "apihelp-main-param-origin": "{{doc-apihelp-param|main|origin}}",
+ "apihelp-main-param-uselang": "{{doc-apihelp-param|main|uselang}}",
+ "apihelp-block-description": "{{doc-apihelp-description|block}}",
+ "apihelp-block-param-user": "{{doc-apihelp-param|block|user}}",
+ "apihelp-block-param-expiry": "{{doc-apihelp-param|block|expiry}}\n{{doc-important|Do not translate \"5 months\", \"2 weeks\", \"infinite\", \"indefinite\" or \"never\"!}}",
+ "apihelp-block-param-reason": "{{doc-apihelp-param|block|reason}}",
+ "apihelp-block-param-anononly": "{{doc-apihelp-param|block|anononly}}\n* See also {{msg-mw|ipb-hardblock}}",
+ "apihelp-block-param-nocreate": "{{doc-apihelp-param|block|nocreate}}\n* See also {{msg-mw|ipbcreateaccount}}",
+ "apihelp-block-param-autoblock": "{{doc-singularthey}}\n{{doc-apihelp-param|block|autoblock}}\n* See also {{msg-mw|ipbenableautoblock}}",
+ "apihelp-block-param-noemail": "{{doc-apihelp-param|block|noemail}}\n* See also {{msg-mw|ipbemailban}}",
+ "apihelp-block-param-hidename": "{{doc-apihelp-param|block|hidename}}",
+ "apihelp-block-param-allowusertalk": "{{doc-apihelp-param|block|allowusertalk}}\n* See also {{msg-mw|ipb-disableusertalk}}",
+ "apihelp-block-param-reblock": "{{doc-apihelp-param|block|reblock}}",
+ "apihelp-block-param-watchuser": "{{doc-apihelp-param|block|watchuser}}",
+ "apihelp-block-example-ip-simple": "{{doc-apihelp-example|block}}",
+ "apihelp-block-example-user-complex": "{{doc-apihelp-example|block}}",
+ "apihelp-checktoken-description": "{{doc-apihelp-description|checktoken}}",
+ "apihelp-checktoken-param-type": "{{doc-apihelp-param|checktoken|type}}",
+ "apihelp-checktoken-param-token": "{{doc-apihelp-param|checktoken|token}}",
+ "apihelp-checktoken-param-maxtokenage": "{{doc-apihelp-param|checktoken|maxtokenage}}",
+ "apihelp-checktoken-example-simple": "{{doc-apihelp-example|checktoken}}",
+ "apihelp-clearhasmsg-description": "{{doc-apihelp-description|clearhasmsg}}",
+ "apihelp-clearhasmsg-example-1": "{{doc-apihelp-example|clearhasmsg}}",
+ "apihelp-compare-description": "{{doc-apihelp-description|compare}}",
+ "apihelp-compare-param-fromtitle": "{{doc-apihelp-param|compare|fromtitle}}",
+ "apihelp-compare-param-fromid": "{{doc-apihelp-param|compare|fromid}}",
+ "apihelp-compare-param-fromrev": "{{doc-apihelp-param|compare|fromrev}}",
+ "apihelp-compare-param-totitle": "{{doc-apihelp-param|compare|totitle}}",
+ "apihelp-compare-param-toid": "{{doc-apihelp-param|compare|toid}}",
+ "apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}",
+ "apihelp-compare-example-1": "{{doc-apihelp-example|compare}}",
+ "apihelp-createaccount-description": "{{doc-apihelp-description|createaccount}}",
+ "apihelp-createaccount-param-name": "{{doc-apihelp-param|createaccount|name}}",
+ "apihelp-createaccount-param-password": "{{doc-apihelp-param|createaccount|password}}",
+ "apihelp-createaccount-param-domain": "{{doc-apihelp-param|createaccount|domain}}",
+ "apihelp-createaccount-param-token": "{{doc-apihelp-param|createaccount|token}}",
+ "apihelp-createaccount-param-email": "{{doc-apihelp-param|createaccount|email}}",
+ "apihelp-createaccount-param-realname": "{{doc-apihelp-param|createaccount|realname}}",
+ "apihelp-createaccount-param-mailpassword": "{{doc-apihelp-param|createaccount|mailpassword}}",
+ "apihelp-createaccount-param-reason": "{{doc-apihelp-param|createaccount|reason}}",
+ "apihelp-createaccount-param-language": "{{doc-apihelp-param|createaccount|language}}",
+ "apihelp-createaccount-example-pass": "{{doc-apihelp-example|createaccount}}",
+ "apihelp-createaccount-example-mail": "{{doc-apihelp-example|createaccount}}",
+ "apihelp-delete-description": "{{doc-apihelp-description|delete}}",
+ "apihelp-delete-param-title": "{{doc-apihelp-param|delete|title}}",
+ "apihelp-delete-param-pageid": "{{doc-apihelp-param|delete|pageid}}",
+ "apihelp-delete-param-reason": "{{doc-apihelp-param|delete|reason}}",
+ "apihelp-delete-param-watch": "{{doc-apihelp-param|delete|watch}}",
+ "apihelp-delete-param-watchlist": "{{doc-apihelp-param|delete|watchlist}}",
+ "apihelp-delete-param-unwatch": "{{doc-apihelp-param|delete|unwatch}}",
+ "apihelp-delete-param-oldimage": "{{doc-apihelp-param|delete|oldimage}}",
+ "apihelp-delete-example-simple": "{{doc-apihelp-example|delete}}",
+ "apihelp-delete-example-reason": "{{doc-apihelp-example|delete}}",
+ "apihelp-disabled-description": "{{doc-apihelp-description|disabled}}",
+ "apihelp-edit-description": "{{doc-apihelp-description|edit}}",
+ "apihelp-edit-param-title": "{{doc-apihelp-param|edit|title}}",
+ "apihelp-edit-param-pageid": "{{doc-apihelp-param|edit|pageid}}",
+ "apihelp-edit-param-section": "{{doc-apihelp-param|edit|section}}",
+ "apihelp-edit-param-sectiontitle": "{{doc-apihelp-param|edit|sectiontitle}}",
+ "apihelp-edit-param-text": "{{doc-apihelp-param|edit|text}}",
+ "apihelp-edit-param-summary": "{{doc-apihelp-param|edit|summary}}",
+ "apihelp-edit-param-tags": "{{doc-apihelp-param|edit|tags}}",
+ "apihelp-edit-param-minor": "{{doc-apihelp-param|edit|minor}}\n{{Identical|Minor edit}}",
+ "apihelp-edit-param-notminor": "{{doc-apihelp-param|edit|notminor}}",
+ "apihelp-edit-param-bot": "{{doc-apihelp-param|edit|bot}}",
+ "apihelp-edit-param-basetimestamp": "{{doc-apihelp-param|edit|basetimestamp}}",
+ "apihelp-edit-param-starttimestamp": "{{doc-apihelp-param|edit|starttimestamp}}",
+ "apihelp-edit-param-recreate": "{{doc-apihelp-param|edit|recreate}}",
+ "apihelp-edit-param-createonly": "{{doc-apihelp-param|edit|createonly}}",
+ "apihelp-edit-param-nocreate": "{{doc-apihelp-param|edit|nocreate}}",
+ "apihelp-edit-param-watch": "{{doc-apihelp-param|edit|watch}}",
+ "apihelp-edit-param-unwatch": "{{doc-apihelp-param|edit|unwatch}}",
+ "apihelp-edit-param-watchlist": "{{doc-apihelp-param|edit|watchlist}}",
+ "apihelp-edit-param-md5": "{{doc-apihelp-param|edit|md5}}",
+ "apihelp-edit-param-prependtext": "{{doc-apihelp-param|edit|prependtext}}",
+ "apihelp-edit-param-appendtext": "{{doc-apihelp-param|edit|appendtext}}",
+ "apihelp-edit-param-undo": "{{doc-apihelp-param|edit|undo}}",
+ "apihelp-edit-param-undoafter": "{{doc-apihelp-param|edit|undoafter}}",
+ "apihelp-edit-param-redirect": "{{doc-apihelp-param|edit|redirect}}",
+ "apihelp-edit-param-contentformat": "{{doc-apihelp-param|edit|contentformat}}",
+ "apihelp-edit-param-contentmodel": "{{doc-apihelp-param|edit|contentmodel}}",
+ "apihelp-edit-param-token": "{{doc-apihelp-param|edit|token}}",
+ "apihelp-edit-example-edit": "{{doc-apihelp-example|edit}}",
+ "apihelp-edit-example-prepend": "{{doc-apihelp-example|edit}}",
+ "apihelp-edit-example-undo": "{{doc-apihelp-example|edit}}",
+ "apihelp-emailuser-description": "{{doc-apihelp-description|emailuser}}",
+ "apihelp-emailuser-param-target": "{{doc-apihelp-param|emailuser|target}}",
+ "apihelp-emailuser-param-subject": "{{doc-apihelp-param|emailuser|subject}}",
+ "apihelp-emailuser-param-text": "{{doc-apihelp-param|emailuser|text}}",
+ "apihelp-emailuser-param-ccme": "{{doc-apihelp-param|emailuser|ccme}}",
+ "apihelp-emailuser-example-email": "{{doc-apihelp-example|emailuser}}",
+ "apihelp-expandtemplates-description": "{{doc-apihelp-description|expandtemplates}}",
+ "apihelp-expandtemplates-param-title": "{{doc-apihelp-param|expandtemplates|title}}",
+ "apihelp-expandtemplates-param-text": "{{doc-apihelp-param|expandtemplates|text}}",
+ "apihelp-expandtemplates-param-revid": "{{doc-apihelp-param|expandtemplates|revid}}\n{{doc-important|Do not translate <code><<nowiki />nowiki>{{<nowiki />REVISIONID}}<<nowiki />/nowiki></code>}}",
+ "apihelp-expandtemplates-param-prop": "{{doc-apihelp-param|expandtemplates|prop}}",
+ "apihelp-expandtemplates-param-includecomments": "{{doc-apihelp-param|expandtemplates|includecomments}}",
+ "apihelp-expandtemplates-param-generatexml": "{{doc-apihelp-param|expandtemplates|generatexml}}",
+ "apihelp-expandtemplates-example-simple": "{{doc-apihelp-example|expandtemplates}}",
+ "apihelp-feedcontributions-description": "{{doc-apihelp-description|feedcontributions}}",
+ "apihelp-feedcontributions-param-feedformat": "{{doc-apihelp-param|feedcontributions|feedformat}}",
+ "apihelp-feedcontributions-param-user": "{{doc-apihelp-param|feedcontributions|user}}",
+ "apihelp-feedcontributions-param-namespace": "{{doc-apihelp-param|feedcontributions|namespace}}",
+ "apihelp-feedcontributions-param-year": "{{doc-apihelp-param|feedcontributions|year}}",
+ "apihelp-feedcontributions-param-month": "{{doc-apihelp-param|feedcontributions|month}}",
+ "apihelp-feedcontributions-param-tagfilter": "{{doc-apihelp-param|feedcontributions|tagfilter}}",
+ "apihelp-feedcontributions-param-deletedonly": "{{doc-apihelp-param|feedcontributions|deletedonly}}",
+ "apihelp-feedcontributions-param-toponly": "{{doc-apihelp-param|feedcontributions|toponly}}",
+ "apihelp-feedcontributions-param-newonly": "{{doc-apihelp-param|feedcontributions|newonly}}",
+ "apihelp-feedcontributions-param-showsizediff": "{{doc-apihelp-param|feedcontributions|showsizediff}}",
+ "apihelp-feedcontributions-example-simple": "{{doc-apihelp-example|feedcontributions}}",
+ "apihelp-feedrecentchanges-description": "{{doc-apihelp-description|feedrecentchanges}}",
+ "apihelp-feedrecentchanges-param-feedformat": "{{doc-apihelp-param|feedrecentchanges|feedformat}}",
+ "apihelp-feedrecentchanges-param-namespace": "{{doc-apihelp-param|feedrecentchanges|namespace}}",
+ "apihelp-feedrecentchanges-param-invert": "{{doc-apihelp-param|feedrecentchanges|invert}}",
+ "apihelp-feedrecentchanges-param-associated": "{{doc-apihelp-param|feedrecentchanges|associated}}",
+ "apihelp-feedrecentchanges-param-days": "{{doc-apihelp-param|feedrecentchanges|days}}",
+ "apihelp-feedrecentchanges-param-limit": "{{doc-apihelp-param|feedrecentchanges|limit}}",
+ "apihelp-feedrecentchanges-param-from": "{{doc-apihelp-param|feedrecentchanges|from}}",
+ "apihelp-feedrecentchanges-param-hideminor": "{{doc-apihelp-param|feedrecentchanges|hideminor}}",
+ "apihelp-feedrecentchanges-param-hidebots": "{{doc-apihelp-param|feedrecentchanges|hidebots}}",
+ "apihelp-feedrecentchanges-param-hideanons": "{{doc-apihelp-param|feedrecentchanges|hideanons}}",
+ "apihelp-feedrecentchanges-param-hideliu": "{{doc-apihelp-param|feedrecentchanges|hideliu}}",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "{{doc-apihelp-param|feedrecentchanges|hidepatrolled}}",
+ "apihelp-feedrecentchanges-param-hidemyself": "{{doc-apihelp-param|feedrecentchanges|hidemyself}}",
+ "apihelp-feedrecentchanges-param-tagfilter": "{{doc-apihelp-param|feedrecentchanges|tagfilter}}",
+ "apihelp-feedrecentchanges-param-target": "{{doc-apihelp-param|feedrecentchanges|target}}",
+ "apihelp-feedrecentchanges-param-showlinkedto": "{{doc-apihelp-param|feedrecentchanges|showlinkedto}}",
+ "apihelp-feedrecentchanges-example-simple": "{{doc-apihelp-example|feedrecentchanges}}",
+ "apihelp-feedrecentchanges-example-30days": "{{doc-apihelp-example|feedrecentchanges}}",
+ "apihelp-feedwatchlist-description": "{{doc-apihelp-description|feedwatchlist}}",
+ "apihelp-feedwatchlist-param-feedformat": "{{doc-apihelp-param|feedwatchlist|feedformat}}",
+ "apihelp-feedwatchlist-param-hours": "{{doc-apihelp-param|feedwatchlist|hours}}",
+ "apihelp-feedwatchlist-param-linktosections": "{{doc-apihelp-param|feedwatchlist|linktosections}}",
+ "apihelp-feedwatchlist-example-default": "{{doc-apihelp-example|feedwatchlist}}",
+ "apihelp-feedwatchlist-example-all6hrs": "{{doc-apihelp-example|feedwatchlist}}",
+ "apihelp-filerevert-description": "{{doc-apihelp-description|filerevert}}",
+ "apihelp-filerevert-param-filename": "{{doc-apihelp-param|filerevert|filename}}",
+ "apihelp-filerevert-param-comment": "{{doc-apihelp-param|filerevert|comment}}",
+ "apihelp-filerevert-param-archivename": "{{doc-apihelp-param|filerevert|archivename}}",
+ "apihelp-filerevert-example-revert": "{{doc-apihelp-example|filerevert}}",
+ "apihelp-help-description": "{{doc-apihelp-description|help}}",
+ "apihelp-help-param-modules": "{{doc-apihelp-param|help|modules}}",
+ "apihelp-help-param-submodules": "{{doc-apihelp-param|help|submodules}}",
+ "apihelp-help-param-recursivesubmodules": "{{doc-apihelp-param|help|recursivesubmodules}}",
+ "apihelp-help-param-helpformat": "{{doc-apihelp-param|help|helpformat}}",
+ "apihelp-help-param-wrap": "{{doc-apihelp-param|help|wrap}}",
+ "apihelp-help-param-toc": "{{doc-apihelp-param|help|toc}}",
+ "apihelp-help-example-main": "{{doc-apihelp-example|help}}",
+ "apihelp-help-example-recursive": "{{doc-apihelp-example|help}}",
+ "apihelp-help-example-help": "{{doc-apihelp-example|help}}",
+ "apihelp-help-example-query": "{{doc-apihelp-example|help}}",
+ "apihelp-imagerotate-description": "{{doc-apihelp-description|imagerotate}}",
+ "apihelp-imagerotate-param-rotation": "{{doc-apihelp-param|imagerotate|rotation}}",
+ "apihelp-imagerotate-example-simple": "{{doc-apihelp-example|imagerotate}}",
+ "apihelp-imagerotate-example-generator": "{{doc-apihelp-example|imagerotate}}",
+ "apihelp-import-description": "{{doc-apihelp-description|import}}",
+ "apihelp-import-param-summary": "{{doc-apihelp-param|import|summary}}",
+ "apihelp-import-param-xml": "{{doc-apihelp-param|import|xml}}",
+ "apihelp-import-param-interwikisource": "{{doc-apihelp-param|import|interwikisource}}",
+ "apihelp-import-param-interwikipage": "{{doc-apihelp-param|import|interwikipage}}",
+ "apihelp-import-param-fullhistory": "{{doc-apihelp-param|import|fullhistory}}",
+ "apihelp-import-param-templates": "{{doc-apihelp-param|import|templates}}",
+ "apihelp-import-param-namespace": "{{doc-apihelp-param|import|namespace}}",
+ "apihelp-import-param-rootpage": "{{doc-apihelp-param|import|rootpage}}",
+ "apihelp-import-example-import": "{{doc-apihelp-example|import}}",
+ "apihelp-login-description": "{{doc-apihelp-description|login}}",
+ "apihelp-login-param-name": "{{doc-apihelp-param|login|name}}\n{{Identical|Username}}",
+ "apihelp-login-param-password": "{{doc-apihelp-param|login|password}}\n{{Identical|Password}}",
+ "apihelp-login-param-domain": "{{doc-apihelp-param|login|domain}}",
+ "apihelp-login-param-token": "{{doc-apihelp-param|login|token}}",
+ "apihelp-login-example-gettoken": "{{doc-apihelp-example|login}}",
+ "apihelp-login-example-login": "{{doc-apihelp-example|login}}",
+ "apihelp-logout-description": "{{doc-apihelp-description|logout}}",
+ "apihelp-logout-example-logout": "{{doc-apihelp-example|logout}}",
+ "apihelp-managetags-description": "{{doc-apihelp-description|managetags}}",
+ "apihelp-managetags-param-operation": "{{doc-apihelp-param|managetags|operation}}",
+ "apihelp-managetags-param-tag": "{{doc-apihelp-param|managetags|tag}}",
+ "apihelp-managetags-param-reason": "{{doc-apihelp-param|managetags|reason}}",
+ "apihelp-managetags-param-ignorewarnings": "{{doc-apihelp-param|managetags|ignorewarnings}}",
+ "apihelp-managetags-example-create": "{{doc-apihelp-example|managetags}}",
+ "apihelp-managetags-example-delete": "{{doc-apihelp-example|managetags|info={{doc-important|The text \"vandlaism\" in this message is intentionally misspelled; the example being documented by this message is the deletion of a misspelled tag.}}}}",
+ "apihelp-managetags-example-activate": "{{doc-apihelp-example|managetags}}",
+ "apihelp-managetags-example-deactivate": "{{doc-apihelp-example|managetags}}",
+ "apihelp-move-description": "{{doc-apihelp-description|move}}",
+ "apihelp-move-param-from": "{{doc-apihelp-param|move|from}}",
+ "apihelp-move-param-fromid": "{{doc-apihelp-param|move|fromid}}",
+ "apihelp-move-param-to": "{{doc-apihelp-param|move|to}}",
+ "apihelp-move-param-reason": "{{doc-apihelp-param|move|reason}}",
+ "apihelp-move-param-movetalk": "{{doc-apihelp-param|move|movetalk}}",
+ "apihelp-move-param-movesubpages": "{{doc-apihelp-param|move|movesubpages}}",
+ "apihelp-move-param-noredirect": "{{doc-apihelp-param|move|noredirect}}",
+ "apihelp-move-param-watch": "{{doc-apihelp-param|move|watch}}",
+ "apihelp-move-param-unwatch": "{{doc-apihelp-param|move|unwatch}}",
+ "apihelp-move-param-watchlist": "{{doc-apihelp-param|move|watchlist}}",
+ "apihelp-move-param-ignorewarnings": "{{doc-apihelp-param|move|ignorewarnings}}",
+ "apihelp-move-example-move": "{{doc-apihelp-example|move}}",
+ "apihelp-opensearch-description": "{{doc-apihelp-description|opensearch}}",
+ "apihelp-opensearch-param-search": "{{doc-apihelp-param|opensearch|search}}",
+ "apihelp-opensearch-param-limit": "{{doc-apihelp-param|opensearch|limit}}",
+ "apihelp-opensearch-param-namespace": "{{doc-apihelp-param|opensearch|namespace}}",
+ "apihelp-opensearch-param-suggest": "{{doc-apihelp-param|opensearch|suggest}}",
+ "apihelp-opensearch-param-redirects": "{{doc-apihelp-param|opensearch|redirects}}",
+ "apihelp-opensearch-param-format": "{{doc-apihelp-param|opensearch|format}}",
+ "apihelp-opensearch-param-warningsaserror": "{{doc-apihelp-param|opensearch|warningsaserror}}",
+ "apihelp-opensearch-example-te": "{{doc-apihelp-example|opensearch}}",
+ "apihelp-options-description": "{{doc-apihelp-description|options}}",
+ "apihelp-options-param-reset": "{{doc-apihelp-param|options|reset}}",
+ "apihelp-options-param-resetkinds": "{{doc-apihelp-param|options|resetkinds}}",
+ "apihelp-options-param-change": "{{doc-apihelp-param|options|change}}",
+ "apihelp-options-param-optionname": "{{doc-apihelp-param|options|optionname}}",
+ "apihelp-options-param-optionvalue": "{{doc-apihelp-param|options|optionvalue}}",
+ "apihelp-options-example-reset": "{{doc-apihelp-example|options}}",
+ "apihelp-options-example-change": "{{doc-apihelp-example|options}}",
+ "apihelp-options-example-complex": "{{doc-apihelp-example|options}}",
+ "apihelp-paraminfo-description": "{{doc-apihelp-description|paraminfo}}",
+ "apihelp-paraminfo-param-modules": "{{doc-apihelp-param|paraminfo|modules}}",
+ "apihelp-paraminfo-param-helpformat": "{{doc-apihelp-param|paraminfo|helpformat}}",
+ "apihelp-paraminfo-param-querymodules": "{{doc-apihelp-param|paraminfo|querymodules}}",
+ "apihelp-paraminfo-param-mainmodule": "{{doc-apihelp-param|paraminfo|mainmodule}}",
+ "apihelp-paraminfo-param-pagesetmodule": "{{doc-apihelp-param|paraminfo|pagesetmodule}}",
+ "apihelp-paraminfo-param-formatmodules": "{{doc-apihelp-param|paraminfo|formatmodules}}",
+ "apihelp-paraminfo-example-1": "{{doc-apihelp-example|paraminfo}}",
+ "apihelp-parse-description": "{{doc-apihelp-description|parse}}",
+ "apihelp-parse-param-title": "{{doc-apihelp-param|parse|title}}",
+ "apihelp-parse-param-text": "{{doc-apihelp-param|parse|text}}",
+ "apihelp-parse-param-summary": "{{doc-apihelp-param|parse|summary}}",
+ "apihelp-parse-param-page": "{{doc-apihelp-param|parse|page}}",
+ "apihelp-parse-param-pageid": "{{doc-apihelp-param|parse|pageid}}",
+ "apihelp-parse-param-redirects": "{{doc-apihelp-param|parse|redirects}}",
+ "apihelp-parse-param-oldid": "{{doc-apihelp-param|parse|oldid}}",
+ "apihelp-parse-param-prop": "{{doc-apihelp-param|parse|prop}}",
+ "apihelp-parse-param-pst": "{{doc-apihelp-param|parse|pst}}",
+ "apihelp-parse-param-onlypst": "{{doc-apihelp-param|parse|onlypst}}",
+ "apihelp-parse-param-effectivelanglinks": "{{doc-apihelp-param|parse|effectivelanglinks}}",
+ "apihelp-parse-param-section": "{{doc-apihelp-param|parse|section}}",
+ "apihelp-parse-param-sectiontitle": "{{doc-apihelp-param|parse|sectiontitle}}",
+ "apihelp-parse-param-disablepp": "{{doc-apihelp-param|parse|disablepp}}",
+ "apihelp-parse-param-disableeditsection": "{{doc-apihelp-param|parse|disableeditsection}}",
+ "apihelp-parse-param-generatexml": "{{doc-apihelp-param|parse|generatexml|params=* $1 - Value of the constant CONTENT_MODEL_WIKITEXT|paramstart=2}}",
+ "apihelp-parse-param-preview": "{{doc-apihelp-param|parse|preview}}",
+ "apihelp-parse-param-sectionpreview": "{{doc-apihelp-param|parse|sectionpreview}}",
+ "apihelp-parse-param-disabletoc": "{{doc-apihelp-param|parse|disabletoc}}",
+ "apihelp-parse-param-contentformat": "{{doc-apihelp-param|parse|contentformat}}",
+ "apihelp-parse-param-contentmodel": "{{doc-apihelp-param|parse|contentmodel}}",
+ "apihelp-parse-example-page": "{{doc-apihelp-example|parse}}",
+ "apihelp-parse-example-text": "{{doc-apihelp-example|parse}}",
+ "apihelp-parse-example-texttitle": "{{doc-apihelp-example|parse}}",
+ "apihelp-parse-example-summary": "{{doc-apihelp-example|parse}}",
+ "apihelp-patrol-description": "{{doc-apihelp-description|patrol}}",
+ "apihelp-patrol-param-rcid": "{{doc-apihelp-param|patrol|rcid}}",
+ "apihelp-patrol-param-revid": "{{doc-apihelp-param|patrol|revid}}",
+ "apihelp-patrol-example-rcid": "{{doc-apihelp-example|patrol}}",
+ "apihelp-patrol-example-revid": "{{doc-apihelp-example|patrol}}",
+ "apihelp-protect-description": "{{doc-apihelp-description|protect}}",
+ "apihelp-protect-param-title": "{{doc-apihelp-param|protect|title}}",
+ "apihelp-protect-param-pageid": "{{doc-apihelp-param|protect|pageid}}",
+ "apihelp-protect-param-protections": "{{doc-apihelp-param|protect|protections}}",
+ "apihelp-protect-param-expiry": "{{doc-apihelp-param|protect|expiry}}",
+ "apihelp-protect-param-reason": "{{doc-apihelp-param|protect|reason}}",
+ "apihelp-protect-param-cascade": "{{doc-apihelp-param|protect|cascade}}",
+ "apihelp-protect-param-watch": "{{doc-apihelp-param|protect|watch}}",
+ "apihelp-protect-param-watchlist": "{{doc-apihelp-param|protect|watchlist}}",
+ "apihelp-protect-example-protect": "{{doc-apihelp-example|protect}}",
+ "apihelp-protect-example-unprotect": "{{doc-apihelp-example|protect}}",
+ "apihelp-protect-example-unprotect2": "{{doc-apihelp-example|protect}}",
+ "apihelp-purge-description": "{{doc-apihelp-description|purge}}",
+ "apihelp-purge-param-forcelinkupdate": "{{doc-apihelp-param|purge|forcelinkupdate}}",
+ "apihelp-purge-param-forcerecursivelinkupdate": "{{doc-apihelp-param|purge|forcerecursivelinkupdate}}",
+ "apihelp-purge-example-simple": "{{doc-apihelp-example|purge}}",
+ "apihelp-purge-example-generator": "{{doc-apihelp-example|purge}}",
+ "apihelp-query-description": "{{doc-apihelp-description|query}}",
+ "apihelp-query-param-prop": "{{doc-apihelp-param|query|prop}}",
+ "apihelp-query-param-list": "{{doc-apihelp-param|query|list}}",
+ "apihelp-query-param-meta": "{{doc-apihelp-param|query|meta}}",
+ "apihelp-query-param-indexpageids": "{{doc-apihelp-param|query|indexpageids}}",
+ "apihelp-query-param-export": "{{doc-apihelp-param|query|export}}",
+ "apihelp-query-param-exportnowrap": "{{doc-apihelp-param|query|exportnowrap}}",
+ "apihelp-query-param-iwurl": "{{doc-apihelp-param|query|iwurl}}",
+ "apihelp-query-param-continue": "{{doc-apihelp-param|query|continue}}",
+ "apihelp-query-param-rawcontinue": "{{doc-apihelp-param|query|rawcontinue}}",
+ "apihelp-query-example-revisions": "{{doc-apihelp-example|query}}",
+ "apihelp-query-example-allpages": "{{doc-apihelp-example|query}}",
+ "apihelp-query+allcategories-description": "{{doc-apihelp-description|query+allcategories}}",
+ "apihelp-query+allcategories-param-from": "{{doc-apihelp-param|query+allcategories|from}}",
+ "apihelp-query+allcategories-param-to": "{{doc-apihelp-param|query+allcategories|to}}",
+ "apihelp-query+allcategories-param-prefix": "{{doc-apihelp-param|query+allcategories|prefix}}",
+ "apihelp-query+allcategories-param-dir": "{{doc-apihelp-param|query+allcategories|dir}}",
+ "apihelp-query+allcategories-param-min": "{{doc-apihelp-param|query+allcategories|min}}",
+ "apihelp-query+allcategories-param-max": "{{doc-apihelp-param|query+allcategories|max}}",
+ "apihelp-query+allcategories-param-limit": "{{doc-apihelp-param|query+allcategories|limit}}",
+ "apihelp-query+allcategories-param-prop": "{{doc-apihelp-param|query+allcategories|prop}}",
+ "apihelp-query+allcategories-example-size": "{{doc-apihelp-example|query+allcategories}}",
+ "apihelp-query+allcategories-example-generator": "{{doc-apihelp-example|query+allcategories}}",
+ "apihelp-query+alldeletedrevisions-description": "{{doc-apihelp-description|query+alldeletedrevisions}}",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "{{doc-apihelp-paraminfo|query+alldeletedrevisions|useronly}}",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "{{doc-apihelp-paraminfo|query+alldeletedrevisions|nonuseronly}}",
+ "apihelp-query+alldeletedrevisions-param-start": "{{doc-apihelp-param|query+alldeletedrevisions|start}}",
+ "apihelp-query+alldeletedrevisions-param-end": "{{doc-apihelp-param|query+alldeletedrevisions|end}}",
+ "apihelp-query+alldeletedrevisions-param-from": "{{doc-apihelp-param|query+alldeletedrevisions|from}}",
+ "apihelp-query+alldeletedrevisions-param-to": "{{doc-apihelp-param|query+alldeletedrevisions|to}}",
+ "apihelp-query+alldeletedrevisions-param-prefix": "{{doc-apihelp-param|query+alldeletedrevisions|prefix}}",
+ "apihelp-query+alldeletedrevisions-param-tag": "{{doc-apihelp-param|query+alldeletedrevisions|tag}}",
+ "apihelp-query+alldeletedrevisions-param-user": "{{doc-apihelp-param|query+alldeletedrevisions|user}}",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "{{doc-apihelp-param|query+alldeletedrevisions|excludeuser}}",
+ "apihelp-query+alldeletedrevisions-param-namespace": "{{doc-apihelp-param|query+alldeletedrevisions|namespace}}",
+ "apihelp-query+alldeletedrevisions-param-miser-user-namespace": "{{doc-apihelp-param|query+alldeletedrevisions|miser-user-namespace}}",
+ "apihelp-query+alldeletedrevisions-param-generatetitles": "{{doc-apihelp-param|query+alldeletedrevisions|generatetitles}}",
+ "apihelp-query+alldeletedrevisions-example-user": "{{doc-apihelp-example|query+alldeletedrevisions}}",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "{{doc-apihelp-example|query+alldeletedrevisions}}",
+ "apihelp-query+allfileusages-description": "{{doc-apihelp-description|query+allfileusages}}",
+ "apihelp-query+allfileusages-param-from": "{{doc-apihelp-param|query+allfileusages|from}}",
+ "apihelp-query+allfileusages-param-to": "{{doc-apihelp-param|query+allfileusages|to}}",
+ "apihelp-query+allfileusages-param-prefix": "{{doc-apihelp-param|query+allfileusages|prefix}}",
+ "apihelp-query+allfileusages-param-unique": "{{doc-apihelp-param|query+allfileusages|unique}}",
+ "apihelp-query+allfileusages-param-prop": "{{doc-apihelp-param|query+allfileusages|prop}}",
+ "apihelp-query+allfileusages-param-limit": "{{doc-apihelp-param|query+allfileusages|limit}}",
+ "apihelp-query+allfileusages-param-dir": "{{doc-apihelp-param|query+allfileusages|dir}}",
+ "apihelp-query+allfileusages-example-B": "{{doc-apihelp-example|query+allfileusages}}",
+ "apihelp-query+allfileusages-example-unique": "{{doc-apihelp-example|query+allfileusages}}",
+ "apihelp-query+allfileusages-example-unique-generator": "{{doc-apihelp-example|query+allfileusages}}",
+ "apihelp-query+allfileusages-example-generator": "{{doc-apihelp-example|query+allfileusages}}",
+ "apihelp-query+allimages-description": "{{doc-apihelp-description|query+allimages}}",
+ "apihelp-query+allimages-param-sort": "{{doc-apihelp-param|query+allimages|sort}}",
+ "apihelp-query+allimages-param-dir": "{{doc-apihelp-param|query+allimages|dir}}",
+ "apihelp-query+allimages-param-from": "{{doc-apihelp-param|query+allimages|from}}",
+ "apihelp-query+allimages-param-to": "{{doc-apihelp-param|query+allimages|to}}",
+ "apihelp-query+allimages-param-start": "{{doc-apihelp-param|query+allimages|start}}",
+ "apihelp-query+allimages-param-end": "{{doc-apihelp-param|query+allimages|end}}",
+ "apihelp-query+allimages-param-prefix": "{{doc-apihelp-param|query+allimages|prefix}}",
+ "apihelp-query+allimages-param-minsize": "{{doc-apihelp-param|query+allimages|minsize}}",
+ "apihelp-query+allimages-param-maxsize": "{{doc-apihelp-param|query+allimages|maxsize}}",
+ "apihelp-query+allimages-param-sha1": "{{doc-apihelp-param|query+allimages|sha1}}",
+ "apihelp-query+allimages-param-sha1base36": "{{doc-apihelp-param|query+allimages|sha1base36}}",
+ "apihelp-query+allimages-param-user": "{{doc-apihelp-param|query+allimages|user}}",
+ "apihelp-query+allimages-param-filterbots": "{{doc-apihelp-param|query+allimages|filterbots}}",
+ "apihelp-query+allimages-param-mime": "{{doc-apihelp-param|query+allimages|mime}}",
+ "apihelp-query+allimages-param-limit": "{{doc-apihelp-param|query+allimages|limit}}",
+ "apihelp-query+allimages-example-B": "{{doc-apihelp-example|query+allimages}}",
+ "apihelp-query+allimages-example-recent": "{{doc-apihelp-example|query+allimages}}",
+ "apihelp-query+allimages-example-mimetypes": "{{doc-apihelp-example|query+allimages}}",
+ "apihelp-query+allimages-example-generator": "{{doc-apihelp-example|query+allimages}}",
+ "apihelp-query+alllinks-description": "{{doc-apihelp-description|query+alllinks}}",
+ "apihelp-query+alllinks-param-from": "{{doc-apihelp-param|query+alllinks|from}}",
+ "apihelp-query+alllinks-param-to": "{{doc-apihelp-param|query+alllinks|to}}",
+ "apihelp-query+alllinks-param-prefix": "{{doc-apihelp-param|query+alllinks|prefix}}",
+ "apihelp-query+alllinks-param-unique": "{{doc-apihelp-param|query+alllinks|unique}}",
+ "apihelp-query+alllinks-param-prop": "{{doc-apihelp-param|query+alllinks|prop}}",
+ "apihelp-query+alllinks-param-namespace": "{{doc-apihelp-param|query+alllinks|namespace}}",
+ "apihelp-query+alllinks-param-limit": "{{doc-apihelp-param|query+alllinks|limit}}",
+ "apihelp-query+alllinks-param-dir": "{{doc-apihelp-param|query+alllinks|dir}}",
+ "apihelp-query+alllinks-example-B": "{{doc-apihelp-example|query+alllinks}}",
+ "apihelp-query+alllinks-example-unique": "{{doc-apihelp-example|query+alllinks}}",
+ "apihelp-query+alllinks-example-unique-generator": "{{doc-apihelp-example|query+alllinks}}",
+ "apihelp-query+alllinks-example-generator": "{{doc-apihelp-example|query+alllinks}}",
+ "apihelp-query+allmessages-description": "{{doc-apihelp-description|query+allmessages}}",
+ "apihelp-query+allmessages-param-messages": "{{doc-apihelp-param|query+allmessages|messages}}",
+ "apihelp-query+allmessages-param-prop": "{{doc-apihelp-param|query+allmessages|prop}}",
+ "apihelp-query+allmessages-param-enableparser": "{{doc-apihelp-param|query+allmessages|enableparser}}",
+ "apihelp-query+allmessages-param-nocontent": "{{doc-apihelp-param|query+allmessages|nocontent}}",
+ "apihelp-query+allmessages-param-includelocal": "{{doc-apihelp-param|query+allmessages|includelocal}}",
+ "apihelp-query+allmessages-param-args": "{{doc-apihelp-param|query+allmessages|args}}",
+ "apihelp-query+allmessages-param-filter": "{{doc-apihelp-param|query+allmessages|filter}}",
+ "apihelp-query+allmessages-param-customised": "{{doc-apihelp-param|query+allmessages|customised}}",
+ "apihelp-query+allmessages-param-lang": "{{doc-apihelp-param|query+allmessages|lang}}",
+ "apihelp-query+allmessages-param-from": "{{doc-apihelp-param|query+allmessages|from}}",
+ "apihelp-query+allmessages-param-to": "{{doc-apihelp-param|query+allmessages|to}}",
+ "apihelp-query+allmessages-param-title": "{{doc-apihelp-param|query+allmessages|title}}",
+ "apihelp-query+allmessages-param-prefix": "{{doc-apihelp-param|query+allmessages|prefix}}",
+ "apihelp-query+allmessages-example-ipb": "{{doc-apihelp-example|query+allmessages}}",
+ "apihelp-query+allmessages-example-de": "{{doc-apihelp-example|query+allmessages}}",
+ "apihelp-query+allpages-description": "{{doc-apihelp-description|query+allpages}}",
+ "apihelp-query+allpages-param-from": "{{doc-apihelp-param|query+allpages|from}}",
+ "apihelp-query+allpages-param-to": "{{doc-apihelp-param|query+allpages|to}}",
+ "apihelp-query+allpages-param-prefix": "{{doc-apihelp-param|query+allpages|prefix}}",
+ "apihelp-query+allpages-param-namespace": "{{doc-apihelp-param|query+allpages|namespace}}",
+ "apihelp-query+allpages-param-filterredir": "{{doc-apihelp-param|query+allpages|filterredir}}",
+ "apihelp-query+allpages-param-minsize": "{{doc-apihelp-param|query+allpages|minsize}}",
+ "apihelp-query+allpages-param-maxsize": "{{doc-apihelp-param|query+allpages|maxsize}}",
+ "apihelp-query+allpages-param-prtype": "{{doc-apihelp-param|query+allpages|prtype}}",
+ "apihelp-query+allpages-param-prlevel": "{{doc-apihelp-param|query+allpages|prlevel}}",
+ "apihelp-query+allpages-param-prfiltercascade": "{{doc-apihelp-param|query+allpages|prfiltercascade}}",
+ "apihelp-query+allpages-param-limit": "{{doc-apihelp-param|query+allpages|limit}}",
+ "apihelp-query+allpages-param-dir": "{{doc-apihelp-param|query+allpages|dir}}",
+ "apihelp-query+allpages-param-filterlanglinks": "{{doc-apihelp-param|query+allpages|filterlanglinks}}",
+ "apihelp-query+allpages-param-prexpiry": "{{doc-apihelp-param|query+allpages|prexpiry}}",
+ "apihelp-query+allpages-example-B": "{{doc-apihelp-example|query+allpages}}",
+ "apihelp-query+allpages-example-generator": "{{doc-apihelp-example|query+allpages}}",
+ "apihelp-query+allpages-example-generator-revisions": "{{doc-apihelp-example|query+allpages}}",
+ "apihelp-query+allredirects-description": "{{doc-apihelp-description|query+allredirects}}",
+ "apihelp-query+allredirects-param-from": "{{doc-apihelp-param|query+allredirects|from}}",
+ "apihelp-query+allredirects-param-to": "{{doc-apihelp-param|query+allredirects|to}}",
+ "apihelp-query+allredirects-param-prefix": "{{doc-apihelp-param|query+allredirects|prefix}}",
+ "apihelp-query+allredirects-param-unique": "{{doc-apihelp-param|query+allredirects|unique}}",
+ "apihelp-query+allredirects-param-prop": "{{doc-apihelp-param|query+allredirects|prop}}",
+ "apihelp-query+allredirects-param-namespace": "{{doc-apihelp-param|query+allredirects|namespace}}",
+ "apihelp-query+allredirects-param-limit": "{{doc-apihelp-param|query+allredirects|limit}}",
+ "apihelp-query+allredirects-param-dir": "{{doc-apihelp-param|query+allredirects|dir}}",
+ "apihelp-query+allredirects-example-B": "{{doc-apihelp-example|query+allredirects}}",
+ "apihelp-query+allredirects-example-unique": "{{doc-apihelp-example|query+allredirects}}",
+ "apihelp-query+allredirects-example-unique-generator": "{{doc-apihelp-example|query+allredirects}}",
+ "apihelp-query+allredirects-example-generator": "{{doc-apihelp-example|query+allredirects}}",
+ "apihelp-query+alltransclusions-description": "{{doc-apihelp-description|query+alltransclusions}}",
+ "apihelp-query+alltransclusions-param-from": "{{doc-apihelp-param|query+alltransclusions|from}}",
+ "apihelp-query+alltransclusions-param-to": "{{doc-apihelp-param|query+alltransclusions|to}}",
+ "apihelp-query+alltransclusions-param-prefix": "{{doc-apihelp-param|query+alltransclusions|prefix}}",
+ "apihelp-query+alltransclusions-param-unique": "{{doc-apihelp-param|query+alltransclusions|unique}}",
+ "apihelp-query+alltransclusions-param-prop": "{{doc-apihelp-param|query+alltransclusions|prop}}",
+ "apihelp-query+alltransclusions-param-namespace": "{{doc-apihelp-param|query+alltransclusions|namespace}}",
+ "apihelp-query+alltransclusions-param-limit": "{{doc-apihelp-param|query+alltransclusions|limit}}",
+ "apihelp-query+alltransclusions-param-dir": "{{doc-apihelp-param|query+alltransclusions|dir}}",
+ "apihelp-query+alltransclusions-example-B": "{{doc-apihelp-example|query+alltransclusions}}",
+ "apihelp-query+alltransclusions-example-unique": "{{doc-apihelp-example|query+alltransclusions}}",
+ "apihelp-query+alltransclusions-example-unique-generator": "{{doc-apihelp-example|query+alltransclusions}}",
+ "apihelp-query+alltransclusions-example-generator": "{{doc-apihelp-example|query+alltransclusions}}",
+ "apihelp-query+allusers-description": "{{doc-apihelp-description|query+allusers}}",
+ "apihelp-query+allusers-param-from": "{{doc-apihelp-param|query+allusers|from}}",
+ "apihelp-query+allusers-param-to": "{{doc-apihelp-param|query+allusers|to}}",
+ "apihelp-query+allusers-param-prefix": "{{doc-apihelp-param|query+allusers|prefix}}",
+ "apihelp-query+allusers-param-dir": "{{doc-apihelp-param|query+allusers|dir}}",
+ "apihelp-query+allusers-param-group": "{{doc-apihelp-param|query+allusers|group}}",
+ "apihelp-query+allusers-param-excludegroup": "{{doc-apihelp-param|query+allusers|excludegroup}}",
+ "apihelp-query+allusers-param-rights": "{{doc-apihelp-param|query+allusers|rights}}",
+ "apihelp-query+allusers-param-prop": "{{doc-apihelp-param|query+allusers|prop}}",
+ "apihelp-query+allusers-param-limit": "{{doc-apihelp-param|query+allusers|limit}}",
+ "apihelp-query+allusers-param-witheditsonly": "{{doc-apihelp-param|query+allusers|witheditsonly}}",
+ "apihelp-query+allusers-param-activeusers": "{{doc-apihelp-param|query+allusers|activeusers|params=* $1 - Value of [[mw:Manual:$wgActiveUserDays]]|paramstart=2}}",
+ "apihelp-query+allusers-example-Y": "{{doc-apihelp-example|query+allusers}}",
+ "apihelp-query+backlinks-description": "{{doc-apihelp-description|query+backlinks}}",
+ "apihelp-query+backlinks-param-title": "{{doc-apihelp-param|query+backlinks|title}}",
+ "apihelp-query+backlinks-param-pageid": "{{doc-apihelp-param|query+backlinks|pageid}}",
+ "apihelp-query+backlinks-param-namespace": "{{doc-apihelp-param|query+backlinks|namespace}}",
+ "apihelp-query+backlinks-param-dir": "{{doc-apihelp-param|query+backlinks|dir}}",
+ "apihelp-query+backlinks-param-filterredir": "{{doc-apihelp-param|query+backlinks|filterredir}}",
+ "apihelp-query+backlinks-param-limit": "{{doc-apihelp-param|query+backlinks|limit}}",
+ "apihelp-query+backlinks-param-redirect": "{{doc-apihelp-param|query+backlinks|redirect}}",
+ "apihelp-query+backlinks-example-simple": "{{doc-apihelp-example|query+backlinks}}",
+ "apihelp-query+backlinks-example-generator": "{{doc-apihelp-example|query+backlinks}}",
+ "apihelp-query+blocks-description": "{{doc-apihelp-description|query+blocks}}",
+ "apihelp-query+blocks-param-start": "{{doc-apihelp-param|query+blocks|start}}",
+ "apihelp-query+blocks-param-end": "{{doc-apihelp-param|query+blocks|end}}",
+ "apihelp-query+blocks-param-ids": "{{doc-apihelp-param|query+blocks|ids}}",
+ "apihelp-query+blocks-param-users": "{{doc-apihelp-param|query+blocks|users}}",
+ "apihelp-query+blocks-param-ip": "{{doc-apihelp-param|query+blocks|ip|params=* $1 - Minimum CIDR prefix for IPv4\n* $2 - Minimum CIDR prefix for IPv6|paramstart=3}}",
+ "apihelp-query+blocks-param-limit": "{{doc-apihelp-param|query+blocks|limit}}",
+ "apihelp-query+blocks-param-prop": "{{doc-apihelp-param|query+blocks|prop}}",
+ "apihelp-query+blocks-param-show": "{{doc-apihelp-param|query+blocks|show}}",
+ "apihelp-query+blocks-example-simple": "{{doc-apihelp-example|query+blocks}}",
+ "apihelp-query+blocks-example-users": "{{doc-apihelp-example|query+blocks}}",
+ "apihelp-query+categories-description": "{{doc-apihelp-description|query+categories}}",
+ "apihelp-query+categories-param-prop": "{{doc-apihelp-param|query+categories|prop}}",
+ "apihelp-query+categories-param-show": "{{doc-apihelp-param|query+categories|show}}",
+ "apihelp-query+categories-param-limit": "{{doc-apihelp-param|query+categories|limit}}",
+ "apihelp-query+categories-param-categories": "{{doc-apihelp-param|query+categories|categories}}",
+ "apihelp-query+categories-param-dir": "{{doc-apihelp-param|query+categories|dir}}",
+ "apihelp-query+categories-example-simple": "{{doc-apihelp-example|query+categories}}",
+ "apihelp-query+categories-example-generator": "{{doc-apihelp-example|query+categories}}",
+ "apihelp-query+categoryinfo-description": "{{doc-apihelp-description|query+categoryinfo}}",
+ "apihelp-query+categoryinfo-example-simple": "{{doc-apihelp-example|query+categoryinfo}}",
+ "apihelp-query+categorymembers-description": "{{doc-apihelp-description|query+categorymembers}}",
+ "apihelp-query+categorymembers-param-title": "{{doc-apihelp-param|query+categorymembers|title}}",
+ "apihelp-query+categorymembers-param-pageid": "{{doc-apihelp-param|query+categorymembers|pageid}}",
+ "apihelp-query+categorymembers-param-prop": "{{doc-apihelp-param|query+categorymembers|prop}}",
+ "apihelp-query+categorymembers-param-namespace": "{{doc-apihelp-param|query+categorymembers|namespace}}",
+ "apihelp-query+categorymembers-param-type": "{{doc-apihelp-param|query+categorymembers|type}}",
+ "apihelp-query+categorymembers-param-limit": "{{doc-apihelp-param|query+categorymembers|limit}}",
+ "apihelp-query+categorymembers-param-sort": "{{doc-apihelp-param|query+categorymembers|sort}}",
+ "apihelp-query+categorymembers-param-dir": "{{doc-apihelp-param|query+categorymembers|dir}}",
+ "apihelp-query+categorymembers-param-start": "{{doc-apihelp-param|query+categorymembers|start}}",
+ "apihelp-query+categorymembers-param-end": "{{doc-apihelp-param|query+categorymembers|end}}",
+ "apihelp-query+categorymembers-param-starthexsortkey": "{{doc-apihelp-param|query+categorymembers|starthexsortkey}}",
+ "apihelp-query+categorymembers-param-endhexsortkey": "{{doc-apihelp-param|query+categorymembers|endhexsortkey}}",
+ "apihelp-query+categorymembers-param-startsortkeyprefix": "{{doc-apihelp-param|query+categorymembers|startsortkeyprefix}}",
+ "apihelp-query+categorymembers-param-endsortkeyprefix": "{{doc-apihelp-param|query+categorymembers|endsortkeyprefix}}",
+ "apihelp-query+categorymembers-param-startsortkey": "{{doc-apihelp-param|query+categorymembers|startsortkey}}",
+ "apihelp-query+categorymembers-param-endsortkey": "{{doc-apihelp-param|query+categorymembers|endsortkey}}",
+ "apihelp-query+categorymembers-example-simple": "{{doc-apihelp-example|query+categorymembers}}",
+ "apihelp-query+categorymembers-example-generator": "{{doc-apihelp-example|query+categorymembers}}",
+ "apihelp-query+contributors-description": "{{doc-apihelp-description|query+contributors}}",
+ "apihelp-query+contributors-param-group": "{{doc-apihelp-param|query+contributors|group}}",
+ "apihelp-query+contributors-param-excludegroup": "{{doc-apihelp-param|query+contributors|excludegroup}}",
+ "apihelp-query+contributors-param-rights": "{{doc-apihelp-param|query+contributors|rights}}",
+ "apihelp-query+contributors-param-excluderights": "{{doc-apihelp-param|query+contributors|excluderights}}",
+ "apihelp-query+contributors-param-limit": "{{doc-apihelp-param|query+contributors|limit}}",
+ "apihelp-query+contributors-example-simple": "{{doc-apihelp-example|query+contributors}}",
+ "apihelp-query+deletedrevisions-description": "{{doc-apihelp-description|query+deletedrevisions}}",
+ "apihelp-query+deletedrevisions-param-start": "{{doc-apihelp-param|query+deletedrevisions|start}}",
+ "apihelp-query+deletedrevisions-param-end": "{{doc-apihelp-param|query+deletedrevisions|end}}",
+ "apihelp-query+deletedrevisions-param-tag": "{{doc-apihelp-param|query+deletedrevisions|tag}}",
+ "apihelp-query+deletedrevisions-param-user": "{{doc-apihelp-param|query+deletedrevisions|user}}",
+ "apihelp-query+deletedrevisions-param-excludeuser": "{{doc-apihelp-param|query+deletedrevisions|excludeuser}}",
+ "apihelp-query+deletedrevisions-param-limit": "{{doc-apihelp-param|query+deletedrevisions|limit}}",
+ "apihelp-query+deletedrevisions-param-prop": "{{doc-apihelp-param|query+deletedrevisions|prop}}",
+ "apihelp-query+deletedrevisions-example-titles": "{{doc-apihelp-example|query+deletedrevisions}}",
+ "apihelp-query+deletedrevisions-example-revids": "{{doc-apihelp-example|query+deletedrevisions}}",
+ "apihelp-query+deletedrevs-description": "{{doc-apihelp-description|query+deletedrevs}}",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{doc-apihelp-paraminfo|query+deletedrevs|modes}}\n{{Identical|Mode}}",
+ "apihelp-query+deletedrevs-param-start": "{{doc-apihelp-param|query+deletedrevs|start}}",
+ "apihelp-query+deletedrevs-param-end": "{{doc-apihelp-param|query+deletedrevs|end}}",
+ "apihelp-query+deletedrevs-param-from": "{{doc-apihelp-param|query+deletedrevs|from}}",
+ "apihelp-query+deletedrevs-param-to": "{{doc-apihelp-param|query+deletedrevs|to}}",
+ "apihelp-query+deletedrevs-param-prefix": "{{doc-apihelp-param|query+deletedrevs|prefix}}",
+ "apihelp-query+deletedrevs-param-unique": "{{doc-apihelp-param|query+deletedrevs|unique}}",
+ "apihelp-query+deletedrevs-param-tag": "{{doc-apihelp-param|query+deletedrevs|tag}}",
+ "apihelp-query+deletedrevs-param-user": "{{doc-apihelp-param|query+deletedrevs|user}}",
+ "apihelp-query+deletedrevs-param-excludeuser": "{{doc-apihelp-param|query+deletedrevs|excludeuser}}",
+ "apihelp-query+deletedrevs-param-namespace": "{{doc-apihelp-param|query+deletedrevs|namespace}}",
+ "apihelp-query+deletedrevs-param-limit": "{{doc-apihelp-param|query+deletedrevs|limit}}",
+ "apihelp-query+deletedrevs-param-prop": "{{doc-apihelp-param|query+deletedrevs|prop}}",
+ "apihelp-query+deletedrevs-example-mode1": "{{doc-apihelp-example|query+deletedrevs}}",
+ "apihelp-query+deletedrevs-example-mode2": "{{doc-apihelp-example|query+deletedrevs}}",
+ "apihelp-query+deletedrevs-example-mode3-main": "{{doc-apihelp-example|query+deletedrevs}}",
+ "apihelp-query+deletedrevs-example-mode3-talk": "{{doc-apihelp-example|query+deletedrevs}}",
+ "apihelp-query+disabled-description": "{{doc-apihelp-description|query+disabled}}",
+ "apihelp-query+duplicatefiles-description": "{{doc-apihelp-description|query+duplicatefiles}}",
+ "apihelp-query+duplicatefiles-param-limit": "{{doc-apihelp-param|query+duplicatefiles|limit}}",
+ "apihelp-query+duplicatefiles-param-dir": "{{doc-apihelp-param|query+duplicatefiles|dir}}",
+ "apihelp-query+duplicatefiles-param-localonly": "{{doc-apihelp-param|query+duplicatefiles|localonly}}",
+ "apihelp-query+duplicatefiles-example-simple": "{{doc-apihelp-example|query+duplicatefiles}}",
+ "apihelp-query+duplicatefiles-example-generated": "{{doc-apihelp-example|query+duplicatefiles}}",
+ "apihelp-query+embeddedin-description": "{{doc-apihelp-description|query+embeddedin}}",
+ "apihelp-query+embeddedin-param-title": "{{doc-apihelp-param|query+embeddedin|title}}",
+ "apihelp-query+embeddedin-param-pageid": "{{doc-apihelp-param|query+embeddedin|pageid}}",
+ "apihelp-query+embeddedin-param-namespace": "{{doc-apihelp-param|query+embeddedin|namespace}}",
+ "apihelp-query+embeddedin-param-dir": "{{doc-apihelp-param|query+embeddedin|dir}}",
+ "apihelp-query+embeddedin-param-filterredir": "{{doc-apihelp-param|query+embeddedin|filterredir}}",
+ "apihelp-query+embeddedin-param-limit": "{{doc-apihelp-param|query+embeddedin|limit}}",
+ "apihelp-query+embeddedin-example-simple": "{{doc-apihelp-example|query+embeddedin}}",
+ "apihelp-query+embeddedin-example-generator": "{{doc-apihelp-example|query+embeddedin}}",
+ "apihelp-query+extlinks-description": "{{doc-apihelp-description|query+extlinks}}",
+ "apihelp-query+extlinks-param-limit": "{{doc-apihelp-param|query+extlinks|limit}}",
+ "apihelp-query+extlinks-param-protocol": "{{doc-apihelp-param|query+extlinks|protocol}}",
+ "apihelp-query+extlinks-param-query": "{{doc-apihelp-param|query+extlinks|query}}",
+ "apihelp-query+extlinks-param-expandurl": "{{doc-apihelp-param|query+extlinks|expandurl}}",
+ "apihelp-query+extlinks-example-simple": "{{doc-apihelp-example|query+extlinks}}",
+ "apihelp-query+exturlusage-description": "{{doc-apihelp-description|query+exturlusage}}",
+ "apihelp-query+exturlusage-param-prop": "{{doc-apihelp-param|query+exturlusage|prop}}",
+ "apihelp-query+exturlusage-param-protocol": "{{doc-apihelp-param|query+exturlusage|protocol}}",
+ "apihelp-query+exturlusage-param-query": "{{doc-apihelp-param|query+exturlusage|query}}",
+ "apihelp-query+exturlusage-param-namespace": "{{doc-apihelp-param|query+exturlusage|namespace}}",
+ "apihelp-query+exturlusage-param-limit": "{{doc-apihelp-param|query+exturlusage|limit}}",
+ "apihelp-query+exturlusage-param-expandurl": "{{doc-apihelp-param|query+exturlusage|expandurl}}",
+ "apihelp-query+exturlusage-example-simple": "{{doc-apihelp-example|query+exturlusage}}",
+ "apihelp-query+filearchive-description": "{{doc-apihelp-description|query+filearchive}}",
+ "apihelp-query+filearchive-param-from": "{{doc-apihelp-param|query+filearchive|from}}",
+ "apihelp-query+filearchive-param-to": "{{doc-apihelp-param|query+filearchive|to}}",
+ "apihelp-query+filearchive-param-prefix": "{{doc-apihelp-param|query+filearchive|prefix}}",
+ "apihelp-query+filearchive-param-limit": "{{doc-apihelp-param|query+filearchive|limit}}",
+ "apihelp-query+filearchive-param-dir": "{{doc-apihelp-param|query+filearchive|dir}}",
+ "apihelp-query+filearchive-param-sha1": "{{doc-apihelp-param|query+filearchive|sha1}}",
+ "apihelp-query+filearchive-param-sha1base36": "{{doc-apihelp-param|query+filearchive|sha1base36}}",
+ "apihelp-query+filearchive-param-prop": "{{doc-apihelp-param|query+filearchive|prop}}",
+ "apihelp-query+filearchive-example-simple": "{{doc-apihelp-example|query+filearchive}}",
+ "apihelp-query+filerepoinfo-description": "{{doc-apihelp-description|query+filerepoinfo}}",
+ "apihelp-query+filerepoinfo-param-prop": "{{doc-apihelp-param|query+filerepoinfo|prop}}",
+ "apihelp-query+filerepoinfo-example-simple": "{{doc-apihelp-example|query+filerepoinfo}}",
+ "apihelp-query+fileusage-description": "{{doc-apihelp-description|query+fileusage}}",
+ "apihelp-query+fileusage-param-prop": "{{doc-apihelp-param|query+fileusage|prop}}",
+ "apihelp-query+fileusage-param-namespace": "{{doc-apihelp-param|query+fileusage|namespace}}",
+ "apihelp-query+fileusage-param-limit": "{{doc-apihelp-param|query+fileusage|limit}}",
+ "apihelp-query+fileusage-param-show": "{{doc-apihelp-param|query+fileusage|show}}",
+ "apihelp-query+fileusage-example-simple": "{{doc-apihelp-example|query+fileusage}}",
+ "apihelp-query+fileusage-example-generator": "{{doc-apihelp-example|query+fileusage}}",
+ "apihelp-query+imageinfo-description": "{{doc-apihelp-description|query+imageinfo}}",
+ "apihelp-query+imageinfo-param-prop": "{{doc-apihelp-param|query+imageinfo|prop|paramvalues=1}}",
+ "apihelp-query+imageinfo-paramvalue-prop-timestamp": "{{doc-apihelp-paramvalue|query+imageinfo|prop|timestamp}}",
+ "apihelp-query+imageinfo-paramvalue-prop-user": "{{doc-apihelp-paramvalue|query+imageinfo|prop|user}}",
+ "apihelp-query+imageinfo-paramvalue-prop-userid": "{{doc-apihelp-paramvalue|query+imageinfo|prop|userid}}",
+ "apihelp-query+imageinfo-paramvalue-prop-comment": "{{doc-apihelp-paramvalue|query+imageinfo|prop|comment}}",
+ "apihelp-query+imageinfo-paramvalue-prop-parsedcomment": "{{doc-apihelp-paramvalue|query+imageinfo|prop|parsedcomment}}",
+ "apihelp-query+imageinfo-paramvalue-prop-canonicaltitle": "{{doc-apihelp-paramvalue|query+imageinfo|prop|canonicaltitle}}",
+ "apihelp-query+imageinfo-paramvalue-prop-url": "{{doc-apihelp-paramvalue|query+imageinfo|prop|url}}",
+ "apihelp-query+imageinfo-paramvalue-prop-size": "{{doc-apihelp-paramvalue|query+imageinfo|prop|size}}",
+ "apihelp-query+imageinfo-paramvalue-prop-dimensions": "{{doc-apihelp-paramvalue|query+imageinfo|prop|dimensions}}",
+ "apihelp-query+imageinfo-paramvalue-prop-sha1": "{{doc-apihelp-paramvalue|query+imageinfo|prop|sha1}}",
+ "apihelp-query+imageinfo-paramvalue-prop-mime": "{{doc-apihelp-paramvalue|query+imageinfo|prop|mime}}",
+ "apihelp-query+imageinfo-paramvalue-prop-thumbmime": "{{doc-apihelp-paramvalue|query+imageinfo|prop|thumbmime}}",
+ "apihelp-query+imageinfo-paramvalue-prop-mediatype": "{{doc-apihelp-paramvalue|query+imageinfo|prop|mediatype}}",
+ "apihelp-query+imageinfo-paramvalue-prop-metadata": "{{doc-apihelp-paramvalue|query+imageinfo|prop|metadata}}",
+ "apihelp-query+imageinfo-paramvalue-prop-commonmetadata": "{{doc-apihelp-paramvalue|query+imageinfo|prop|commonmetadata}}",
+ "apihelp-query+imageinfo-paramvalue-prop-extmetadata": "{{doc-apihelp-paramvalue|query+imageinfo|prop|extmetadata}}",
+ "apihelp-query+imageinfo-paramvalue-prop-archivename": "{{doc-apihelp-paramvalue|query+imageinfo|prop|archivename}}",
+ "apihelp-query+imageinfo-paramvalue-prop-bitdepth": "{{doc-apihelp-paramvalue|query+imageinfo|prop|bitdepth}}",
+ "apihelp-query+imageinfo-paramvalue-prop-uploadwarning": "{{doc-apihelp-paramvalue|query+imageinfo|prop|uploadwarning}}",
+ "apihelp-query+imageinfo-param-limit": "{{doc-apihelp-param|query+imageinfo|limit}}",
+ "apihelp-query+imageinfo-param-start": "{{doc-apihelp-param|query+imageinfo|start}}",
+ "apihelp-query+imageinfo-param-end": "{{doc-apihelp-param|query+imageinfo|end}}",
+ "apihelp-query+imageinfo-param-urlwidth": "{{doc-apihelp-param|query+imageinfo|urlwidth|params=* $1 - Maximum number of thumbnails per query|paramstart=2}}",
+ "apihelp-query+imageinfo-param-urlheight": "{{doc-apihelp-param|query+imageinfo|urlheight}}",
+ "apihelp-query+imageinfo-param-metadataversion": "{{doc-apihelp-param|query+imageinfo|metadataversion}}",
+ "apihelp-query+imageinfo-param-extmetadatalanguage": "{{doc-apihelp-param|query+imageinfo|extmetadatalanguage}}",
+ "apihelp-query+imageinfo-param-extmetadatamultilang": "{{doc-apihelp-param|query+imageinfo|extmetadatamultilang}}",
+ "apihelp-query+imageinfo-param-extmetadatafilter": "{{doc-apihelp-param|query+imageinfo|extmetadatafilter}}",
+ "apihelp-query+imageinfo-param-urlparam": "{{doc-apihelp-param|query+imageinfo|urlparam}}",
+ "apihelp-query+imageinfo-param-localonly": "{{doc-apihelp-param|query+imageinfo|localonly}}",
+ "apihelp-query+imageinfo-example-simple": "{{doc-apihelp-example|query+imageinfo}}",
+ "apihelp-query+imageinfo-example-dated": "{{doc-apihelp-example|query+imageinfo}}",
+ "apihelp-query+images-description": "{{doc-apihelp-description|query+images}}",
+ "apihelp-query+images-param-limit": "{{doc-apihelp-param|query+images|limit}}",
+ "apihelp-query+images-param-images": "{{doc-apihelp-param|query+images|images}}",
+ "apihelp-query+images-param-dir": "{{doc-apihelp-param|query+images|dir}}",
+ "apihelp-query+images-example-simple": "{{doc-apihelp-example|query+images}}",
+ "apihelp-query+images-example-generator": "{{doc-apihelp-example|query+images}}",
+ "apihelp-query+imageusage-description": "{{doc-apihelp-description|query+imageusage}}",
+ "apihelp-query+imageusage-param-title": "{{doc-apihelp-param|query+imageusage|title}}",
+ "apihelp-query+imageusage-param-pageid": "{{doc-apihelp-param|query+imageusage|pageid}}",
+ "apihelp-query+imageusage-param-namespace": "{{doc-apihelp-param|query+imageusage|namespace}}",
+ "apihelp-query+imageusage-param-dir": "{{doc-apihelp-param|query+imageusage|dir}}",
+ "apihelp-query+imageusage-param-filterredir": "{{doc-apihelp-param|query+imageusage|filterredir}}",
+ "apihelp-query+imageusage-param-limit": "{{doc-apihelp-param|query+imageusage|limit}}",
+ "apihelp-query+imageusage-param-redirect": "{{doc-apihelp-param|query+imageusage|redirect}}",
+ "apihelp-query+imageusage-example-simple": "{{doc-apihelp-example|query+imageusage}}",
+ "apihelp-query+imageusage-example-generator": "{{doc-apihelp-example|query+imageusage}}",
+ "apihelp-query+info-description": "{{doc-apihelp-description|query+info}}",
+ "apihelp-query+info-param-prop": "{{doc-apihelp-param|query+info|prop|paramvalues=1}}",
+ "apihelp-query+info-paramvalue-prop-protection": "{{doc-apihelp-paramvalue|query+info|prop|protection}}",
+ "apihelp-query+info-paramvalue-prop-talkid": "{{doc-apihelp-paramvalue|query+info|prop|talkid}}",
+ "apihelp-query+info-paramvalue-prop-watched": "{{doc-apihelp-paramvalue|query+info|prop|watched}}",
+ "apihelp-query+info-paramvalue-prop-watchers": "{{doc-apihelp-paramvalue|query+info|prop|watchers}}",
+ "apihelp-query+info-paramvalue-prop-notificationtimestamp": "{{doc-apihelp-paramvalue|query+info|prop|notificationtimestamp}}",
+ "apihelp-query+info-paramvalue-prop-subjectid": "{{doc-apihelp-paramvalue|query+info|prop|subjectid}}",
+ "apihelp-query+info-paramvalue-prop-url": "{{doc-apihelp-paramvalue|query+info|prop|url}}",
+ "apihelp-query+info-paramvalue-prop-readable": "{{doc-apihelp-paramvalue|query+info|prop|readable}}",
+ "apihelp-query+info-paramvalue-prop-preload": "{{doc-apihelp-paramvalue|query+info|prop|preload}}",
+ "apihelp-query+info-paramvalue-prop-displaytitle": "{{doc-apihelp-paramvalue|query+info|prop|displaytitle}}",
+ "apihelp-query+info-param-testactions": "{{doc-apihelp-param|query+info|testactions}}",
+ "apihelp-query+info-param-token": "{{doc-apihelp-param|query+info|token}}",
+ "apihelp-query+info-example-simple": "{{doc-apihelp-example|query+info}}",
+ "apihelp-query+info-example-protection": "{{doc-apihelp-example|query+info}}",
+ "apihelp-query+iwbacklinks-description": "{{doc-apihelp-description|query+iwbacklinks}}",
+ "apihelp-query+iwbacklinks-param-prefix": "{{doc-apihelp-param|query+iwbacklinks|prefix}}",
+ "apihelp-query+iwbacklinks-param-title": "{{doc-apihelp-param|query+iwbacklinks|title}}",
+ "apihelp-query+iwbacklinks-param-limit": "{{doc-apihelp-param|query+iwbacklinks|limit}}",
+ "apihelp-query+iwbacklinks-param-prop": "{{doc-apihelp-param|query+iwbacklinks|prop}}",
+ "apihelp-query+iwbacklinks-param-dir": "{{doc-apihelp-param|query+iwbacklinks|dir}}",
+ "apihelp-query+iwbacklinks-example-simple": "{{doc-apihelp-example|query+iwbacklinks}}",
+ "apihelp-query+iwbacklinks-example-generator": "{{doc-apihelp-example|query+iwbacklinks}}",
+ "apihelp-query+iwlinks-description": "{{doc-apihelp-description|query+iwlinks}}",
+ "apihelp-query+iwlinks-param-url": "{{doc-apihelp-param|query+iwlinks|url}}",
+ "apihelp-query+iwlinks-param-prop": "{{doc-apihelp-param|query+iwlinks|prop}}",
+ "apihelp-query+iwlinks-param-limit": "{{doc-apihelp-param|query+iwlinks|limit}}",
+ "apihelp-query+iwlinks-param-prefix": "{{doc-apihelp-param|query+iwlinks|prefix}}",
+ "apihelp-query+iwlinks-param-title": "{{doc-apihelp-param|query+iwlinks|title}}",
+ "apihelp-query+iwlinks-param-dir": "{{doc-apihelp-param|query+iwlinks|dir}}",
+ "apihelp-query+iwlinks-example-simple": "{{doc-apihelp-example|query+iwlinks}}",
+ "apihelp-query+langbacklinks-description": "{{doc-apihelp-description|query+langbacklinks}}",
+ "apihelp-query+langbacklinks-param-lang": "{{doc-apihelp-param|query+langbacklinks|lang}}",
+ "apihelp-query+langbacklinks-param-title": "{{doc-apihelp-param|query+langbacklinks|title}}",
+ "apihelp-query+langbacklinks-param-limit": "{{doc-apihelp-param|query+langbacklinks|limit}}",
+ "apihelp-query+langbacklinks-param-prop": "{{doc-apihelp-param|query+langbacklinks|prop}}",
+ "apihelp-query+langbacklinks-param-dir": "{{doc-apihelp-param|query+langbacklinks|dir}}",
+ "apihelp-query+langbacklinks-example-simple": "{{doc-apihelp-example|query+langbacklinks}}",
+ "apihelp-query+langbacklinks-example-generator": "{{doc-apihelp-example|query+langbacklinks}}",
+ "apihelp-query+langlinks-description": "{{doc-apihelp-description|query+langlinks}}",
+ "apihelp-query+langlinks-param-limit": "{{doc-apihelp-param|query+langlinks|limit}}",
+ "apihelp-query+langlinks-param-url": "{{doc-apihelp-param|query+langlinks|url}}",
+ "apihelp-query+langlinks-param-prop": "{{doc-apihelp-param|query+langlinks|prop}}",
+ "apihelp-query+langlinks-param-lang": "{{doc-apihelp-param|query+langlinks|lang}}",
+ "apihelp-query+langlinks-param-title": "{{doc-apihelp-param|query+langlinks|title}}",
+ "apihelp-query+langlinks-param-dir": "{{doc-apihelp-param|query+langlinks|dir}}",
+ "apihelp-query+langlinks-param-inlanguagecode": "{{doc-apihelp-param|query+langlinks|inlanguagecode}}",
+ "apihelp-query+langlinks-example-simple": "{{doc-apihelp-example|query+langlinks}}",
+ "apihelp-query+links-description": "{{doc-apihelp-description|query+links}}",
+ "apihelp-query+links-param-namespace": "{{doc-apihelp-param|query+links|namespace}}",
+ "apihelp-query+links-param-limit": "{{doc-apihelp-param|query+links|limit}}",
+ "apihelp-query+links-param-titles": "{{doc-apihelp-param|query+links|titles}}",
+ "apihelp-query+links-param-dir": "{{doc-apihelp-param|query+links|dir}}",
+ "apihelp-query+links-example-simple": "{{doc-apihelp-example|query+links}}",
+ "apihelp-query+links-example-generator": "{{doc-apihelp-example|query+links}}",
+ "apihelp-query+links-example-namespaces": "{{doc-apihelp-example|query+links}}",
+ "apihelp-query+linkshere-description": "{{doc-apihelp-description|query+linkshere}}",
+ "apihelp-query+linkshere-param-prop": "{{doc-apihelp-param|query+linkshere|prop}}",
+ "apihelp-query+linkshere-param-namespace": "{{doc-apihelp-param|query+linkshere|namespace}}",
+ "apihelp-query+linkshere-param-limit": "{{doc-apihelp-param|query+linkshere|limit}}",
+ "apihelp-query+linkshere-param-show": "{{doc-apihelp-param|query+linkshere|show}}",
+ "apihelp-query+linkshere-example-simple": "{{doc-apihelp-example|query+linkshere}}",
+ "apihelp-query+linkshere-example-generator": "{{doc-apihelp-example|query+linkshere}}",
+ "apihelp-query+logevents-description": "{{doc-apihelp-description|query+logevents}}",
+ "apihelp-query+logevents-param-prop": "{{doc-apihelp-param|query+logevents|prop}}",
+ "apihelp-query+logevents-param-type": "{{doc-apihelp-param|query+logevents|type}}",
+ "apihelp-query+logevents-param-action": "{{doc-apihelp-param|query+logevents|action}}",
+ "apihelp-query+logevents-param-start": "{{doc-apihelp-param|query+logevents|start}}",
+ "apihelp-query+logevents-param-end": "{{doc-apihelp-param|query+logevents|end}}",
+ "apihelp-query+logevents-param-user": "{{doc-apihelp-param|query+logevents|user}}",
+ "apihelp-query+logevents-param-title": "{{doc-apihelp-param|query+logevents|title}}",
+ "apihelp-query+logevents-param-namespace": "{{doc-apihelp-param|query+logevents|namespace}}",
+ "apihelp-query+logevents-param-prefix": "{{doc-apihelp-param|query+logevents|prefix}}",
+ "apihelp-query+logevents-param-tag": "{{doc-apihelp-param|query+logevents|tag}}",
+ "apihelp-query+logevents-param-limit": "{{doc-apihelp-param|query+logevents|limit}}",
+ "apihelp-query+logevents-example-simple": "{{doc-apihelp-example|query+logevents}}",
+ "apihelp-query+pagepropnames-description": "{{doc-apihelp-description|query+pagepropnames}}",
+ "apihelp-query+pagepropnames-param-limit": "{{doc-apihelp-param|query+pagepropnames|limit}}",
+ "apihelp-query+pagepropnames-example-simple": "{{doc-apihelp-example|query+pagepropnames}}",
+ "apihelp-query+pageprops-description": "{{doc-apihelp-description|query+pageprops}}",
+ "apihelp-query+pageprops-param-prop": "{{doc-apihelp-param|query+pageprops|prop}}",
+ "apihelp-query+pageprops-example-simple": "{{doc-apihelp-example|query+pageprops}}",
+ "apihelp-query+pageswithprop-description": "{{doc-apihelp-description|query+pageswithprop}}",
+ "apihelp-query+pageswithprop-param-propname": "{{doc-apihelp-param|query+pageswithprop|propname}}",
+ "apihelp-query+pageswithprop-param-prop": "{{doc-apihelp-param|query+pageswithprop|prop}}",
+ "apihelp-query+pageswithprop-param-limit": "{{doc-apihelp-param|query+pageswithprop|limit}}",
+ "apihelp-query+pageswithprop-param-dir": "{{doc-apihelp-param|query+pageswithprop|dir}}",
+ "apihelp-query+pageswithprop-example-simple": "{{doc-apihelp-example|query+pageswithprop}}",
+ "apihelp-query+pageswithprop-example-generator": "{{doc-apihelp-example|query+pageswithprop}}",
+ "apihelp-query+prefixsearch-description": "{{doc-apihelp-description|query+prefixsearch}}",
+ "apihelp-query+prefixsearch-param-search": "{{doc-apihelp-param|query+prefixsearch|search}}",
+ "apihelp-query+prefixsearch-param-namespace": "{{doc-apihelp-param|query+prefixsearch|namespace}}",
+ "apihelp-query+prefixsearch-param-limit": "{{doc-apihelp-param|query+prefixsearch|limit}}",
+ "apihelp-query+prefixsearch-param-offset": "{{doc-apihelp-param|query+prefixsearch|offset}}",
+ "apihelp-query+prefixsearch-example-simple": "{{doc-apihelp-example|query+prefixsearch}}",
+ "apihelp-query+protectedtitles-description": "{{doc-apihelp-description|query+protectedtitles}}",
+ "apihelp-query+protectedtitles-param-namespace": "{{doc-apihelp-param|query+protectedtitles|namespace}}",
+ "apihelp-query+protectedtitles-param-level": "{{doc-apihelp-param|query+protectedtitles|level}}",
+ "apihelp-query+protectedtitles-param-limit": "{{doc-apihelp-param|query+protectedtitles|limit}}",
+ "apihelp-query+protectedtitles-param-start": "{{doc-apihelp-param|query+protectedtitles|start}}",
+ "apihelp-query+protectedtitles-param-end": "{{doc-apihelp-param|query+protectedtitles|end}}",
+ "apihelp-query+protectedtitles-param-prop": "{{doc-apihelp-param|query+protectedtitles|prop}}",
+ "apihelp-query+protectedtitles-example-simple": "{{doc-apihelp-example|query+protectedtitles}}",
+ "apihelp-query+protectedtitles-example-generator": "{{doc-apihelp-example|query+protectedtitles}}",
+ "apihelp-query+querypage-description": "{{doc-apihelp-description|query+querypage}}",
+ "apihelp-query+querypage-param-page": "{{doc-apihelp-param|query+querypage|page}}",
+ "apihelp-query+querypage-param-limit": "{{doc-apihelp-param|query+querypage|limit}}",
+ "apihelp-query+querypage-example-ancientpages": "{{doc-apihelp-example|query+querypage}}",
+ "apihelp-query+random-description": "{{doc-apihelp-description|query+random}}",
+ "apihelp-query+random-param-namespace": "{{doc-apihelp-param|query+random|namespace}}",
+ "apihelp-query+random-param-limit": "{{doc-apihelp-param|query+random|limit}}",
+ "apihelp-query+random-param-redirect": "{{doc-apihelp-param|query+random|redirect}}",
+ "apihelp-query+random-example-simple": "{{doc-apihelp-example|query+random}}",
+ "apihelp-query+random-example-generator": "{{doc-apihelp-example|query+random}}",
+ "apihelp-query+recentchanges-description": "{{doc-apihelp-description|query+recentchanges}}",
+ "apihelp-query+recentchanges-param-start": "{{doc-apihelp-param|query+recentchanges|start}}",
+ "apihelp-query+recentchanges-param-end": "{{doc-apihelp-param|query+recentchanges|end}}",
+ "apihelp-query+recentchanges-param-namespace": "{{doc-apihelp-param|query+recentchanges|namespace}}",
+ "apihelp-query+recentchanges-param-user": "{{doc-apihelp-param|query+recentchanges|user}}",
+ "apihelp-query+recentchanges-param-excludeuser": "{{doc-apihelp-param|query+recentchanges|excludeuser}}",
+ "apihelp-query+recentchanges-param-tag": "{{doc-apihelp-param|query+recentchanges|tag}}",
+ "apihelp-query+recentchanges-param-prop": "{{doc-apihelp-param|query+recentchanges|prop}}",
+ "apihelp-query+recentchanges-param-token": "{{doc-apihelp-param|query+recentchanges|token}}",
+ "apihelp-query+recentchanges-param-show": "{{doc-apihelp-param|query+recentchanges|show}}",
+ "apihelp-query+recentchanges-param-limit": "{{doc-apihelp-param|query+recentchanges|limit}}",
+ "apihelp-query+recentchanges-param-type": "{{doc-apihelp-param|query+recentchanges|type}}",
+ "apihelp-query+recentchanges-param-toponly": "{{doc-apihelp-param|query+recentchanges|toponly}}",
+ "apihelp-query+recentchanges-example-simple": "{{doc-apihelp-example|query+recentchanges}}",
+ "apihelp-query+recentchanges-example-generator": "{{doc-apihelp-example|query+recentchanges}}",
+ "apihelp-query+redirects-description": "{{doc-apihelp-description|query+redirects}}",
+ "apihelp-query+redirects-param-prop": "{{doc-apihelp-param|query+redirects|prop}}",
+ "apihelp-query+redirects-param-namespace": "{{doc-apihelp-param|query+redirects|namespace}}",
+ "apihelp-query+redirects-param-limit": "{{doc-apihelp-param|query+redirects|limit}}",
+ "apihelp-query+redirects-param-show": "{{doc-apihelp-param|query+redirects|show}}",
+ "apihelp-query+redirects-example-simple": "{{doc-apihelp-example|query+redirects}}",
+ "apihelp-query+redirects-example-generator": "{{doc-apihelp-example|query+redirects}}",
+ "apihelp-query+revisions-description": "{{doc-apihelp-description|query+revisions}}",
+ "apihelp-query+revisions-paraminfo-singlepageonly": "{{doc-apihelp-paraminfo|query+revisions|singlepageonly}}",
+ "apihelp-query+revisions-param-startid": "{{doc-apihelp-param|query+revisions|startid}}",
+ "apihelp-query+revisions-param-endid": "{{doc-apihelp-param|query+revisions|endid}}",
+ "apihelp-query+revisions-param-start": "{{doc-apihelp-param|query+revisions|start}}",
+ "apihelp-query+revisions-param-end": "{{doc-apihelp-param|query+revisions|end}}",
+ "apihelp-query+revisions-param-user": "{{doc-apihelp-param|query+revisions|user}}",
+ "apihelp-query+revisions-param-excludeuser": "{{doc-apihelp-param|query+revisions|excludeuser}}",
+ "apihelp-query+revisions-param-tag": "{{doc-apihelp-param|query+revisions|tag}}",
+ "apihelp-query+revisions-param-token": "{{doc-apihelp-param|query+revisions|token}}",
+ "apihelp-query+revisions-example-content": "{{doc-apihelp-example|query+revisions}}",
+ "apihelp-query+revisions-example-last5": "{{doc-apihelp-example|query+revisions}}",
+ "apihelp-query+revisions-example-first5": "{{doc-apihelp-example|query+revisions}}",
+ "apihelp-query+revisions-example-first5-after": "{{doc-apihelp-example|query+revisions}}",
+ "apihelp-query+revisions-example-first5-not-localhost": "{{doc-apihelp-example|query+revisions}}",
+ "apihelp-query+revisions-example-first5-user": "{{doc-apihelp-example|query+revisions}}",
+ "apihelp-query+revisions+base-param-prop": "{{doc-apihelp-param|query+revisions+base|prop|description=the \"prop\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-limit": "{{doc-apihelp-param|query+revisions+base|limit|description=the \"limit\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-expandtemplates": "{{doc-apihelp-param|query+revisions+base|expandtemplates|description=the \"expandtemplates\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-generatexml": "{{doc-apihelp-param|query+revisions+base|generatexml|description=the \"generatexml\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-parse": "{{doc-apihelp-param|query+revisions+base|parse|description=the \"parse\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-section": "{{doc-apihelp-param|query+revisions+base|section|description=the \"section\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-diffto": "{{doc-apihelp-param|query+revisions+base|diffto|description=the \"diffto\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-difftotext": "{{doc-apihelp-param|query+revisions+base|difftotext|description=the \"difftotext\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+revisions+base-param-contentformat": "{{doc-apihelp-param|query+revisions+base|contentformat|description=the \"contentformat\" parameter to revision querying modules|noseealso=1}}",
+ "apihelp-query+search-description": "{{doc-apihelp-description|query+search}}",
+ "apihelp-query+search-param-search": "{{doc-apihelp-param|query+search|search}}",
+ "apihelp-query+search-param-namespace": "{{doc-apihelp-param|query+search|namespace}}",
+ "apihelp-query+search-param-what": "{{doc-apihelp-param|query+search|what}}",
+ "apihelp-query+search-param-info": "{{doc-apihelp-param|query+search|info}}",
+ "apihelp-query+search-param-prop": "{{doc-apihelp-param|query+search|prop}}",
+ "apihelp-query+search-param-limit": "{{doc-apihelp-param|query+search|limit}}",
+ "apihelp-query+search-param-interwiki": "{{doc-apihelp-param|query+search|interwiki}}",
+ "apihelp-query+search-param-backend": "{{doc-apihelp-param|query+search|backend}}",
+ "apihelp-query+search-example-simple": "{{doc-apihelp-example|query+search}}",
+ "apihelp-query+search-example-text": "{{doc-apihelp-example|query+search}}",
+ "apihelp-query+search-example-generator": "{{doc-apihelp-example|query+search}}",
+ "apihelp-query+siteinfo-description": "{{doc-apihelp-description|query+siteinfo}}",
+ "apihelp-query+siteinfo-param-prop": "{{doc-apihelp-param|query+siteinfo|prop}}",
+ "apihelp-query+siteinfo-param-filteriw": "{{doc-apihelp-param|query+siteinfo|filteriw}}",
+ "apihelp-query+siteinfo-param-showalldb": "{{doc-apihelp-param|query+siteinfo|showalldb}}",
+ "apihelp-query+siteinfo-param-numberingroup": "{{doc-apihelp-param|query+siteinfo|numberingroup}}",
+ "apihelp-query+siteinfo-param-inlanguagecode": "{{doc-apihelp-param|query+siteinfo|inlanguagecode}}",
+ "apihelp-query+siteinfo-example-simple": "{{doc-apihelp-example|query+siteinfo}}",
+ "apihelp-query+siteinfo-example-interwiki": "{{doc-apihelp-example|query+siteinfo}}",
+ "apihelp-query+siteinfo-example-replag": "{{doc-apihelp-example|query+siteinfo}}",
+ "apihelp-query+stashimageinfo-description": "{{doc-apihelp-description|query+stashimageinfo}}",
+ "apihelp-query+stashimageinfo-param-filekey": "{{doc-apihelp-param|query+stashimageinfo|filekey}}",
+ "apihelp-query+stashimageinfo-param-sessionkey": "{{doc-apihelp-param|query+stashimageinfo|sessionkey}}",
+ "apihelp-query+stashimageinfo-example-simple": "{{doc-apihelp-example|query+stashimageinfo}}",
+ "apihelp-query+stashimageinfo-example-params": "{{doc-apihelp-example|query+stashimageinfo}}",
+ "apihelp-query+tags-description": "{{doc-apihelp-description|query+tags}}",
+ "apihelp-query+tags-param-limit": "{{doc-apihelp-param|query+tags|limit}}",
+ "apihelp-query+tags-param-prop": "{{doc-apihelp-param|query+tags|prop}}",
+ "apihelp-query+tags-example-simple": "{{doc-apihelp-example|query+tags}}",
+ "apihelp-query+templates-description": "{{doc-apihelp-description|query+templates}}",
+ "apihelp-query+templates-param-namespace": "{{doc-apihelp-param|query+templates|namespace}}",
+ "apihelp-query+templates-param-limit": "{{doc-apihelp-param|query+templates|limit}}",
+ "apihelp-query+templates-param-templates": "{{doc-apihelp-param|query+templates|templates}}",
+ "apihelp-query+templates-param-dir": "{{doc-apihelp-param|query+templates|dir}}",
+ "apihelp-query+templates-example-simple": "{{doc-apihelp-example|query+templates}}",
+ "apihelp-query+templates-example-generator": "{{doc-apihelp-example|query+templates}}",
+ "apihelp-query+templates-example-namespaces": "{{doc-apihelp-example|query+templates}}",
+ "apihelp-query+tokens-description": "{{doc-apihelp-description|query+tokens}}",
+ "apihelp-query+tokens-param-type": "{{doc-apihelp-param|query+tokens|type}}",
+ "apihelp-query+tokens-example-simple": "{{doc-apihelp-example|query+tokens}}",
+ "apihelp-query+tokens-example-types": "{{doc-apihelp-example|query+tokens}}",
+ "apihelp-query+transcludedin-description": "{{doc-apihelp-description|query+transcludedin}}",
+ "apihelp-query+transcludedin-param-prop": "{{doc-apihelp-param|query+transcludedin|prop}}",
+ "apihelp-query+transcludedin-param-namespace": "{{doc-apihelp-param|query+transcludedin|namespace}}",
+ "apihelp-query+transcludedin-param-limit": "{{doc-apihelp-param|query+transcludedin|limit}}",
+ "apihelp-query+transcludedin-param-show": "{{doc-apihelp-param|query+transcludedin|show}}",
+ "apihelp-query+transcludedin-example-simple": "{{doc-apihelp-example|query+transcludedin}}",
+ "apihelp-query+transcludedin-example-generator": "{{doc-apihelp-example|query+transcludedin}}",
+ "apihelp-query+usercontribs-description": "{{doc-apihelp-description|query+usercontribs}}",
+ "apihelp-query+usercontribs-param-limit": "{{doc-apihelp-param|query+usercontribs|limit}}",
+ "apihelp-query+usercontribs-param-start": "{{doc-apihelp-param|query+usercontribs|start}}",
+ "apihelp-query+usercontribs-param-end": "{{doc-apihelp-param|query+usercontribs|end}}",
+ "apihelp-query+usercontribs-param-user": "{{doc-apihelp-param|query+usercontribs|user}}",
+ "apihelp-query+usercontribs-param-userprefix": "{{doc-apihelp-param|query+usercontribs|userprefix}}",
+ "apihelp-query+usercontribs-param-namespace": "{{doc-apihelp-param|query+usercontribs|namespace}}",
+ "apihelp-query+usercontribs-param-prop": "{{doc-apihelp-param|query+usercontribs|prop}}",
+ "apihelp-query+usercontribs-param-show": "{{doc-apihelp-param|query+usercontribs|show|params=* $1 - Value of [[mw:Manual:$RCMaxAge|$RCMaxAge]]|paramstart=2}}",
+ "apihelp-query+usercontribs-param-tag": "{{doc-apihelp-param|query+usercontribs|tag}}",
+ "apihelp-query+usercontribs-param-toponly": "{{doc-apihelp-param|query+usercontribs|toponly}}",
+ "apihelp-query+usercontribs-example-user": "{{doc-apihelp-example|query+usercontribs}}",
+ "apihelp-query+usercontribs-example-ipprefix": "{{doc-apihelp-example|query+usercontribs}}",
+ "apihelp-query+userinfo-description": "{{doc-apihelp-description|query+userinfo}}",
+ "apihelp-query+userinfo-param-prop": "{{doc-apihelp-param|query+userinfo|prop|params=* $1 - Maximum value for the \"unreadcount\" property.\n$2 - Return value when there are more unread pages.|paramstart=3}}",
+ "apihelp-query+userinfo-example-simple": "{{doc-apihelp-example|query+userinfo}}",
+ "apihelp-query+userinfo-example-data": "{{doc-apihelp-example|query+userinfo}}",
+ "apihelp-query+users-description": "{{doc-apihelp-description|query+users}}",
+ "apihelp-query+users-param-prop": "{{doc-apihelp-param|query+users|prop}}",
+ "apihelp-query+users-param-users": "{{doc-apihelp-param|query+users|users}}",
+ "apihelp-query+users-param-token": "{{doc-apihelp-param|query+users|token}}",
+ "apihelp-query+users-example-simple": "{{doc-apihelp-example|query+users}}",
+ "apihelp-query+watchlist-description": "{{doc-apihelp-description|query+watchlist}}",
+ "apihelp-query+watchlist-param-allrev": "{{doc-apihelp-param|query+watchlist|allrev}}",
+ "apihelp-query+watchlist-param-start": "{{doc-apihelp-param|query+watchlist|start}}",
+ "apihelp-query+watchlist-param-end": "{{doc-apihelp-param|query+watchlist|end}}",
+ "apihelp-query+watchlist-param-namespace": "{{doc-apihelp-param|query+watchlist|namespace}}",
+ "apihelp-query+watchlist-param-user": "{{doc-apihelp-param|query+watchlist|user}}",
+ "apihelp-query+watchlist-param-excludeuser": "{{doc-apihelp-param|query+watchlist|excludeuser}}",
+ "apihelp-query+watchlist-param-limit": "{{doc-apihelp-param|query+watchlist|limit}}",
+ "apihelp-query+watchlist-param-prop": "{{doc-apihelp-param|query+watchlist|prop}}",
+ "apihelp-query+watchlist-param-show": "{{doc-apihelp-param|query+watchlist|show}}",
+ "apihelp-query+watchlist-param-type": "{{doc-apihelp-param|query+watchlist|type}}",
+ "apihelp-query+watchlist-param-owner": "{{doc-apihelp-param|query+watchlist|owner}}",
+ "apihelp-query+watchlist-param-token": "{{doc-apihelp-param|query+watchlist|token}}",
+ "apihelp-query+watchlist-example-simple": "{{doc-apihelp-example|query+watchlist}}",
+ "apihelp-query+watchlist-example-props": "{{doc-apihelp-example|query+watchlist}}",
+ "apihelp-query+watchlist-example-allrev": "{{doc-apihelp-example|query+watchlist}}",
+ "apihelp-query+watchlist-example-generator": "{{doc-apihelp-example|query+watchlist}}",
+ "apihelp-query+watchlist-example-generator-rev": "{{doc-apihelp-example|query+watchlist}}",
+ "apihelp-query+watchlist-example-wlowner": "{{doc-apihelp-example|query+watchlist}}",
+ "apihelp-query+watchlistraw-description": "{{doc-apihelp-description|query+watchlistraw}}",
+ "apihelp-query+watchlistraw-param-namespace": "{{doc-apihelp-param|query+watchlistraw|namespace}}",
+ "apihelp-query+watchlistraw-param-limit": "{{doc-apihelp-param|query+watchlistraw|limit}}",
+ "apihelp-query+watchlistraw-param-prop": "{{doc-apihelp-param|query+watchlistraw|prop}}",
+ "apihelp-query+watchlistraw-param-show": "{{doc-apihelp-param|query+watchlistraw|show}}",
+ "apihelp-query+watchlistraw-param-owner": "{{doc-apihelp-param|query+watchlistraw|owner}}",
+ "apihelp-query+watchlistraw-param-token": "{{doc-apihelp-param|query+watchlistraw|token}}",
+ "apihelp-query+watchlistraw-example-simple": "{{doc-apihelp-example|query+watchlistraw}}",
+ "apihelp-query+watchlistraw-example-generator": "{{doc-apihelp-example|query+watchlistraw}}",
+ "apihelp-revisiondelete-description": "{{doc-apihelp-description|revisiondelete}}",
+ "apihelp-revisiondelete-param-type": "{{doc-apihelp-param|revisiondelete|type}}",
+ "apihelp-revisiondelete-param-target": "{{doc-apihelp-param|revisiondelete|target}}",
+ "apihelp-revisiondelete-param-ids": "{{doc-apihelp-param|revisiondelete|ids}}",
+ "apihelp-revisiondelete-param-hide": "{{doc-apihelp-param|revisiondelete|hide}}",
+ "apihelp-revisiondelete-param-show": "{{doc-apihelp-param|revisiondelete|show}}",
+ "apihelp-revisiondelete-param-suppress": "{{doc-apihelp-param|revisiondelete|suppress}}",
+ "apihelp-revisiondelete-param-reason": "{{doc-apihelp-param|revisiondelete|reason}}",
+ "apihelp-revisiondelete-example-revision": "{{doc-apihelp-example|revisiondelete}}",
+ "apihelp-revisiondelete-example-log": "{{doc-apihelp-example|revisiondelete}}",
+ "apihelp-rollback-description": "{{doc-apihelp-description|rollback}}",
+ "apihelp-rollback-param-title": "{{doc-apihelp-param|rollback|title}}",
+ "apihelp-rollback-param-pageid": "{{doc-apihelp-param|rollback|pageid}}",
+ "apihelp-rollback-param-user": "{{doc-apihelp-param|rollback|user}}",
+ "apihelp-rollback-param-summary": "{{doc-apihelp-param|rollback|summary}}",
+ "apihelp-rollback-param-markbot": "{{doc-apihelp-param|rollback|markbot}}",
+ "apihelp-rollback-param-watchlist": "{{doc-apihelp-param|rollback|watchlist}}",
+ "apihelp-rollback-example-simple": "{{doc-apihelp-example|rollback}}",
+ "apihelp-rollback-example-summary": "{{doc-apihelp-example|rollback}}",
+ "apihelp-rsd-description": "{{doc-apihelp-description|rsd}}",
+ "apihelp-rsd-example-simple": "{{doc-apihelp-example|rsd}}",
+ "apihelp-setnotificationtimestamp-description": "{{doc-apihelp-description|setnotificationtimestamp}}",
+ "apihelp-setnotificationtimestamp-param-entirewatchlist": "{{doc-apihelp-param|setnotificationtimestamp|entirewatchlist}}",
+ "apihelp-setnotificationtimestamp-param-timestamp": "{{doc-apihelp-param|setnotificationtimestamp|timestamp}}",
+ "apihelp-setnotificationtimestamp-param-torevid": "{{doc-apihelp-param|setnotificationtimestamp|torevid}}",
+ "apihelp-setnotificationtimestamp-param-newerthanrevid": "{{doc-apihelp-param|setnotificationtimestamp|newerthanrevid}}",
+ "apihelp-setnotificationtimestamp-example-all": "{{doc-apihelp-example|setnotificationtimestamp}}",
+ "apihelp-setnotificationtimestamp-example-page": "{{doc-apihelp-example|setnotificationtimestamp}}",
+ "apihelp-setnotificationtimestamp-example-pagetimestamp": "{{doc-apihelp-example|setnotificationtimestamp}}",
+ "apihelp-setnotificationtimestamp-example-allpages": "{{doc-apihelp-example|setnotificationtimestamp}}",
+ "apihelp-tag-description": "{{doc-apihelp-description|tag}}",
+ "apihelp-tag-param-rcid": "{{doc-apihelp-param|tag|rcid}}",
+ "apihelp-tag-param-revid": "{{doc-apihelp-param|tag|revid}}",
+ "apihelp-tag-param-logid": "{{doc-apihelp-param|tag|logid}}",
+ "apihelp-tag-param-add": "{{doc-apihelp-param|tag|add}}",
+ "apihelp-tag-param-remove": "{{doc-apihelp-param|tag|remove}}",
+ "apihelp-tag-param-reason": "{{doc-apihelp-param|tag|reason}}",
+ "apihelp-tag-example-rev": "{{doc-apihelp-example|tag}}",
+ "apihelp-tag-example-log": "{{doc-apihelp-example|tag}}",
+ "apihelp-tokens-description": "{{doc-apihelp-description|tokens}}",
+ "apihelp-tokens-param-type": "{{doc-apihelp-param|tokens|type}}",
+ "apihelp-tokens-example-edit": "{{doc-apihelp-example|tokens}}",
+ "apihelp-tokens-example-emailmove": "{{doc-apihelp-example|tokens}}",
+ "apihelp-unblock-description": "{{doc-apihelp-description|unblock}}",
+ "apihelp-unblock-param-id": "{{doc-apihelp-param|unblock|id}}",
+ "apihelp-unblock-param-user": "{{doc-apihelp-param|unblock|user}}",
+ "apihelp-unblock-param-reason": "{{doc-apihelp-param|unblock|reason}}",
+ "apihelp-unblock-example-id": "{{doc-apihelp-example|unblock}}",
+ "apihelp-unblock-example-user": "{{doc-apihelp-example|unblock}}",
+ "apihelp-undelete-description": "{{doc-apihelp-description|undelete}}",
+ "apihelp-undelete-param-title": "{{doc-apihelp-param|undelete|title}}",
+ "apihelp-undelete-param-reason": "{{doc-apihelp-param|undelete|reason}}",
+ "apihelp-undelete-param-timestamps": "{{doc-apihelp-param|undelete|timestamps}}",
+ "apihelp-undelete-param-fileids": "{{doc-apihelp-param|undelete|fileids}}",
+ "apihelp-undelete-param-watchlist": "{{doc-apihelp-param|undelete|watchlist}}",
+ "apihelp-undelete-example-page": "{{doc-apihelp-example|undelete}}",
+ "apihelp-undelete-example-revisions": "{{doc-apihelp-example|undelete}}",
+ "apihelp-upload-description": "{{doc-apihelp-description|upload}}",
+ "apihelp-upload-param-filename": "{{doc-apihelp-param|upload|filename}}",
+ "apihelp-upload-param-comment": "{{doc-apihelp-param|upload|comment}}",
+ "apihelp-upload-param-text": "{{doc-apihelp-param|upload|text}}",
+ "apihelp-upload-param-watch": "{{doc-apihelp-param|upload|watch}}",
+ "apihelp-upload-param-watchlist": "{{doc-apihelp-param|upload|watchlist}}",
+ "apihelp-upload-param-ignorewarnings": "{{doc-apihelp-param|upload|ignorewarnings}}",
+ "apihelp-upload-param-file": "{{doc-apihelp-param|upload|file}}",
+ "apihelp-upload-param-url": "{{doc-apihelp-param|upload|url}}",
+ "apihelp-upload-param-filekey": "{{doc-apihelp-param|upload|filekey}}",
+ "apihelp-upload-param-sessionkey": "{{doc-apihelp-param|upload|sessionkey}}",
+ "apihelp-upload-param-stash": "{{doc-apihelp-param|upload|stash}}",
+ "apihelp-upload-param-filesize": "{{doc-apihelp-param|upload|filesize}}",
+ "apihelp-upload-param-offset": "{{doc-apihelp-param|upload|offset}}",
+ "apihelp-upload-param-chunk": "{{doc-apihelp-param|upload|chunk}}",
+ "apihelp-upload-param-async": "{{doc-apihelp-param|upload|async}}",
+ "apihelp-upload-param-asyncdownload": "{{doc-apihelp-param|upload|asyncdownload}}",
+ "apihelp-upload-param-leavemessage": "{{doc-apihelp-param|upload|leavemessage}}",
+ "apihelp-upload-param-statuskey": "{{doc-apihelp-param|upload|statuskey}}",
+ "apihelp-upload-param-checkstatus": "{{doc-apihelp-param|upload|checkstatus}}",
+ "apihelp-upload-example-url": "{{doc-apihelp-example|upload}}",
+ "apihelp-upload-example-filekey": "{{doc-apihelp-example|upload}}",
+ "apihelp-userrights-description": "{{doc-apihelp-description|userrights}}",
+ "apihelp-userrights-param-user": "{{doc-apihelp-param|userrights|user}}\n{{Identical|Username}}",
+ "apihelp-userrights-param-userid": "{{doc-apihelp-param|userrights|userid}}\n{{Identical|User ID}}",
+ "apihelp-userrights-param-add": "{{doc-apihelp-param|userrights|add}}",
+ "apihelp-userrights-param-remove": "{{doc-apihelp-param|userrights|remove}}",
+ "apihelp-userrights-param-reason": "{{doc-apihelp-param|userrights|reason}}",
+ "apihelp-userrights-example-user": "{{doc-apihelp-example|userrights}}",
+ "apihelp-userrights-example-userid": "{{doc-apihelp-example|userrights}}",
+ "apihelp-watch-description": "{{doc-apihelp-description|watch}}",
+ "apihelp-watch-param-title": "{{doc-apihelp-param|watch|title}}",
+ "apihelp-watch-param-unwatch": "{{doc-apihelp-param|watch|unwatch}}",
+ "apihelp-watch-example-watch": "{{doc-apihelp-example|watch}}",
+ "apihelp-watch-example-unwatch": "{{doc-apihelp-example|watch}}",
+ "apihelp-watch-example-generator": "{{doc-apihelp-example|watch}}",
+ "apihelp-format-example-generic": "{{doc-apihelp-example|format|params=* $1 - Format name|paramstart=2|noseealso=1}}",
+ "apihelp-dbg-description": "{{doc-apihelp-description|dbg|seealso=* {{msg-mw|apihelp-dbgfm-description}}}}",
+ "apihelp-dbgfm-description": "{{doc-apihelp-description|dbgfm|seealso=* {{msg-mw|apihelp-dbg-description}}}}",
+ "apihelp-dump-description": "{{doc-apihelp-description|dump|seealso=* {{msg-mw|apihelp-dumpfm-description}}}}",
+ "apihelp-dumpfm-description": "{{doc-apihelp-description|dumpfm|seealso=* {{msg-mw|apihelp-dump-description}}}}",
+ "apihelp-json-description": "{{doc-apihelp-description|json|seealso=* {{msg-mw|apihelp-jsonfm-description}}}}",
+ "apihelp-json-param-callback": "{{doc-apihelp-param|json|callback}}",
+ "apihelp-json-param-utf8": "{{doc-apihelp-param|json|utf8}}",
+ "apihelp-json-param-ascii": "{{doc-apihelp-param|json|ascii}}",
+ "apihelp-json-param-formatversion": "{{doc-apihelp-param|json|formatversion}}",
+ "apihelp-jsonfm-description": "{{doc-apihelp-description|jsonfm|seealso=* {{msg-mw|apihelp-json-description}}}}",
+ "apihelp-none-description": "{{doc-apihelp-description|none}}",
+ "apihelp-php-description": "{{doc-apihelp-description|php|seealso=* {{msg-mw|apihelp-phpfm-description}}}}",
+ "apihelp-php-param-formatversion": "{{doc-apihelp-param|json|formatversion}}",
+ "apihelp-phpfm-description": "{{doc-apihelp-description|phpfm|seealso=* {{msg-mw|apihelp-php-description}}}}",
+ "apihelp-rawfm-description": "{{doc-apihelp-description|rawfm|seealso=* {{msg-mw|apihelp-raw-description}}}}",
+ "apihelp-txt-description": "{{doc-apihelp-description|txt|seealso=* {{msg-mw|apihelp-txtfm-description}}}}",
+ "apihelp-txtfm-description": "{{doc-apihelp-description|txtfm|seealso=* {{msg-mw|apihelp-txt-description}}}}",
+ "apihelp-wddx-description": "{{doc-apihelp-description|wddx|seealso=* {{msg-mw|apihelp-wddxfm-description}}}}",
+ "apihelp-wddxfm-description": "{{doc-apihelp-description|wddxfm|seealso=* {{msg-mw|apihelp-wddx-description}}}}",
+ "apihelp-xml-description": "{{doc-apihelp-description|xml|seealso=* {{msg-mw|apihelp-xmlfm-description}}}}",
+ "apihelp-xml-param-xslt": "{{doc-apihelp-param|xml|xslt}}",
+ "apihelp-xml-param-includexmlnamespace": "{{doc-apihelp-param|xml|includexmlnamespace}}",
+ "apihelp-xmlfm-description": "{{doc-apihelp-description|xmlfm|seealso=* {{msg-mw|apihelp-xml-description}}}}",
+ "apihelp-yaml-description": "{{doc-apihelp-description|yaml|seealso=* {{msg-mw|apihelp-yamlfm-description}}}}",
+ "apihelp-yamlfm-description": "{{doc-apihelp-description|yamlfm|seealso=* {{msg-mw|apihelp-yaml-description}}}}",
+ "api-format-title": "{{technical}}\nPage title when API output is pretty-printed in HTML.",
+ "api-format-prettyprint-header": "{{technical}} Displayed as a header when API output is pretty-printed in HTML.\n\nParameters:\n* $1 - Format name\n* $2 - Non-pretty-printing module name",
+ "api-orm-param-props": "{{doc-apihelp-param|orm|props|description=the \"props\" parameter in subclasses of ApiQueryORM}}",
+ "api-orm-param-limit": "{{doc-apihelp-param|orm|limit|description=the \"limit\" parameter in subclasses of ApiQueryORM}}",
+ "api-pageset-param-titles": "{{doc-apihelp-param|pageset|titles|description=the \"titles\" parameter in pageset-using modules}}",
+ "api-pageset-param-pageids": "{{doc-apihelp-param|pageset|pageids|description=the \"pageids\" parameter in pageset-using modules}}",
+ "api-pageset-param-revids": "{{doc-apihelp-param|pageset|revids|description=the \"revids\" parameter in pageset-using modules}}",
+ "api-pageset-param-generator": "{{doc-apihelp-param|pageset|generator|description=the \"generator\" parameter in pageset-using modules}}",
+ "api-pageset-param-redirects-generator": "{{doc-apihelp-param|pageset|redirects-generator|description=the \"redirects\" parameter in pageset-using modules when the \"generator\" parameter is also available}}",
+ "api-pageset-param-redirects-nogenerator": "{{doc-apihelp-param|pageset|redirects-generator|description=the \"redirects\" parameter in pageset-using modules when the \"generator\" parameter is not available}}",
+ "api-pageset-param-converttitles": "{{doc-apihelp-param|pageset|converttitles|description=the \"converttitles\" parameter in pageset-using modules|params=* $1 - List of languages with variants|paramstart=2}}",
+ "api-help-title": "Page title for the auto-generated help output",
+ "api-help-lead": "Text displayed at the top of the API help page",
+ "api-help-main-header": "Text for the header of the main module",
+ "api-help-fallback-description": "{{notranslate}}",
+ "api-help-fallback-parameter": "{{notranslate}}",
+ "api-help-fallback-example": "{{notranslate}}",
+ "api-help-flags": "{{optional}} Label for the API help flags box\n\nParameters:\n* $1 - Number of flags to be displayed",
+ "api-help-flag-deprecated": "Flag displayed for an API module that is deprecated",
+ "api-help-flag-internal": "Flag displayed for an API module that is considered internal or unstable",
+ "api-help-flag-readrights": "Flag displayed for an API module that requires read rights",
+ "api-help-flag-writerights": "Flag displayed for an API module that requires write rights",
+ "api-help-flag-mustbeposted": "Flag displayed for an API module that only accepts POST requests",
+ "api-help-flag-generator": "Flag displayed for an API module that can be used as a generator",
+ "api-help-help-urls": "{{optional}} Label for the API help urls section\n\nParameters:\n* $1 - Number of urls to be displayed",
+ "api-help-parameters": "Label for the API help parameters section\n\nParameters:\n* $1 - Number of parameters to be displayed\n{{Identical|Parameter}}",
+ "api-help-param-deprecated": "Displayed in the API help for any deprecated parameter\n{{Identical|Deprecated}}",
+ "api-help-param-required": "Displayed in the API help for any required parameter",
+ "api-help-param-list": "Used to display the possible values for a parameter taking a list of values\n\nParameters:\n* $1 - 1 if the parameter takes one value, 2 if the parameter takes any number of values\n* $2 - Comma-separated list of values, possibly formatted using {{msg-mw|api-help-param-list-can-be-empty}}\n{{Identical|Value}}",
+ "api-help-param-list-can-be-empty": "Used to indicate that one of the possible values in the list is the empty string.\n\nParameters:\n* $1 - Number of items in the rest of the list; may be 0\n* $2 - Remainder of the list as a comma-separated string",
+ "api-help-param-limit": "Used to display the maximum value of a limit parameter\n\nParameters:\n* $1 - Maximum value",
+ "api-help-param-limit2": "Used to display the maximum values of a limit parameter\n\nParameters:\n* $1 - Maximum value without the apihighlimits right\n* $2 - Maximum value with the apihighlimits right",
+ "api-help-param-integer-min": "Used to display an integer parameter with a minimum but no maximum value\n\nParameters:\n* $1 - 1 if the parameter takes one value, 2 if the parameter takes any number of values\n* $2 - Minimum value\n* $3 - unused\n\nSee also:\n* {{msg-mw|api-help-param-integer-max}}\n* {{msg-mw|api-help-param-integer-minmax}}",
+ "api-help-param-integer-max": "Used to display an integer parameter with a maximum but no minimum value.\n\nParameters:\n* $1 - 1 if the parameter takes one value, 2 if the parameter takes any number of values\n* $2 - (Unused)\n* $3 - Maximum value\nSee also:\n* {{msg-mw|Api-help-param-integer-min}}\n* {{msg-mw|Api-help-param-integer-minmax}}",
+ "api-help-param-integer-minmax": "Used to display an integer parameter with a maximum and minimum values\n\nParameters:\n* $1 - 1 if the parameter takes one value, 2 if the parameter takes any number of values\n* $2 - Minimum value\n* $3 - Maximum value\n\nSee also:\n* {{msg-mw|api-help-param-integer-min}}\n* {{msg-mw|api-help-param-integer-max}}",
+ "api-help-param-upload": "{{technical}} Used to indicate that an 'upload'-type parameter must be posted as a file upload using multipart/form-data",
+ "api-help-param-multi-separate": "Used to indicate how to separate multiple values. Not used with {{msg-mw|api-help-param-list}}.",
+ "api-help-param-multi-max": "Used to indicate the maximum number of values accepted for a multi-valued parameter.\n\nParameters:\n* $1 - Maximum value without the apihighlimits right\n* $2 - Maximum value with the apihighlimits right",
+ "api-help-param-default": "Used to display the default value for an API parameter\n\nParameters:\n* $1 - Default value\n\nSee also:\n* {{msg-mw|api-help-param-default-empty}}\n{{Identical|Default}}",
+ "api-help-param-default-empty": "Used to display the default value for an API parameter when that default is an empty value\n\nSee also:\n* {{msg-mw|api-help-param-default}}",
+ "api-help-param-token": "{{doc-apihelp-param|description=any 'token' parameter|paramstart=2|params=\n* $1 - Token type|noseealso=1}}",
+ "api-help-param-token-webui": "{{doc-apihelp-param|description=additional text for any \"token\" parameter, explaining that web UI tokens are also accepted|noseealso=1}}",
+ "api-help-param-disabled-in-miser-mode": "{{doc-apihelp-param|description=any parameter that is disabled when [[mw:Manual:$wgMiserMode|$wgMiserMode]] is set.|noseealso=1}}",
+ "api-help-param-limited-in-miser-mode": "{{doc-apihelp-param|description=additional text for any parameter that may cause the module to return few results when [[mw:Manual:$wgMiserMode|$wgMiserMode]] is set.|noseealso=1}}",
+ "api-help-param-direction": "{{doc-apihelp-param|description=any standard \"dir\" parameter|noseealso=1}}",
+ "api-help-param-continue": "{{doc-apihelp-param|description=any standard \"continue\" parameter, or other parameter with the same semantics|noseealso=1}}",
+ "api-help-param-no-description": "Displayed on API parameters that lack any description",
+ "api-help-examples": "Label for the API help examples section\n\nParameters:\n* $1 - Number of examples to be displayed\n{{Identical|Example}}",
+ "api-help-permissions": "Label for the \"permissions\" section in the main module's help output.\n\nParameters:\n* $1 - Number of permissions displayed\n{{Identical|Permission}}",
+ "api-help-permissions-granted-to": "Used to introduce the list of groups each permission is assigned to.\n\nParameters:\n* $1 - Number of groups\n* $2 - List of group names, comma-separated",
+ "api-help-right-apihighlimits": "{{technical}}{{doc-right|apihighlimits|prefix=api-help}}\nThis message is used instead of {{msg-mw|right-apihighlimits}} in the API help to display the actual limits.\n\nParameters:\n* $1 - Limit for slow queries\n* $2 - Limit for fast queries",
+ "api-credits-header": "Header for the API credits section in the API help output\n{{Identical|Credit}}",
+ "api-credits": "API credits text, displayed in the API help output"
+}
diff --git a/includes/api/i18n/roa-tara.json b/includes/api/i18n/roa-tara.json
new file mode 100644
index 00000000..07064122
--- /dev/null
+++ b/includes/api/i18n/roa-tara.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Joetaras"
+ ]
+ },
+ "apihelp-block-param-reason": "Mutive pu blocche.",
+ "apihelp-createaccount-param-name": "Nome de l'utende.",
+ "apihelp-edit-param-text": "Vôsce.",
+ "apihelp-edit-example-edit": "Cange 'na pàgene"
+}
diff --git a/includes/api/i18n/ru.json b/includes/api/i18n/ru.json
new file mode 100644
index 00000000..e533d799
--- /dev/null
+++ b/includes/api/i18n/ru.json
@@ -0,0 +1,55 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mahairod",
+ "Okras",
+ "Eakarpov",
+ "Kaganer"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Документация]]\n* [[mw:API:FAQ|ЧаВО]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Почтовая рассылка]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Новости API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Ошибки и запросы]\n</div>\n<strong>Статус:</strong> Все отображаемые на этой странице функции должны работать, однако API находится в статусе активной разработки, и может измениться в любой момент. Подпишитесь на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\n<strong>Ошибочные запросы:</strong> Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом \"MediaWiki-API-Error\", после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:API:Errors_and_warnings|API: Ошибки и предупреждения]].",
+ "apihelp-main-param-action": "Действие, которое следует выполнить.",
+ "apihelp-main-param-format": "Формат вывода.",
+ "apihelp-main-param-smaxage": "Устанавливает заголовок <code>s-maxage</code> в заданное число секунд. Ошибки никогда не кэшируются.",
+ "apihelp-main-param-maxage": "Устанавливает заголовок <code>max-age</code> в заданное число секунд. Ошибки никогда не кэшируются.",
+ "apihelp-main-param-assert": "Удостовериться, что пользователь авторизован, если задано <kbd>user</kbd>, или что имеет права бота, если задано <kbd>bot</kbd>.",
+ "apihelp-main-param-requestid": "Любое заданное здесь значение будет включено в ответ. Может быть использовано для различения запросов.",
+ "apihelp-main-param-servedby": "Включить в результаты имя хоста, обработавшего запрос.",
+ "apihelp-main-param-curtimestamp": "Включить в результаты временную метку.",
+ "apihelp-main-param-origin": "При обращении к API, используя кросс-доменный AJAX-запрос (CORS), задайте параметру значение исходного домена. Он должен быть включён в любой предварительный запрос и таким образом должен быть частью URI-запроса (не тела POST). Он должен точно соответствовать одному из источников в заголовке <code>Origin<code>, так что он должен быть задан наподобие <kbd>https://ru.wikipedia.org</kbd> или <kbd>https://meta.wikimedia.org</kbd>. Если параметр не соответствует заголовку <code>Origin<code>, будет возвращён ответ с кодом ошибки 403. Если параметр соответствует заголовку <code>Origin</code>, и источник находится в белом списке, будет установлен заголовок <code>Access-Control-Allow-Origin</code>.",
+ "apihelp-block-description": "Блокировка участника.",
+ "apihelp-block-param-user": "Имя участника, IP-адрес или диапазон IP-адресов, которые вы хотите заблокировать.",
+ "apihelp-block-param-reason": "Причина блокировки.",
+ "apihelp-block-param-nocreate": "Запретить создание учётных записей.",
+ "apihelp-createaccount-param-name": "Имя участника.",
+ "apihelp-delete-description": "Удалить страницу.",
+ "apihelp-delete-param-watch": "Добавить страницу к текущему списку наблюдения пользователя.",
+ "apihelp-disabled-description": "Этот модуль был отключен.",
+ "apihelp-edit-param-sectiontitle": "Заголовок для нового раздела.",
+ "apihelp-edit-param-text": "Содержание страницы.",
+ "apihelp-edit-param-minor": "Незначительное изменение (малая правка).",
+ "apihelp-edit-param-notminor": "Значительное изменение (обычная, не «малая», правка).",
+ "apihelp-edit-param-bot": "Пометить правку как сделанную ботом.",
+ "apihelp-edit-param-watch": "Добавить страницу к текущему списку наблюдения пользователя.",
+ "apihelp-edit-example-edit": "Редактировать страницу",
+ "apihelp-expandtemplates-param-title": "Заголовок страницы.",
+ "apihelp-import-param-xml": "Загруженный XML-файл.",
+ "apihelp-login-param-name": "Имя участника.",
+ "apihelp-login-param-password": "Пароль.",
+ "apihelp-login-param-domain": "Домен (необязательно).",
+ "apihelp-login-example-login": "Войти",
+ "apihelp-logout-description": "Выйти и очистить данные сессии.",
+ "apihelp-query+alllinks-example-unique-generator": "Получить все названия-ссылки, выделяя пропущенные.",
+ "apihelp-query+duplicatefiles-example-generated": "Поиск дубликатов всех файлов.",
+ "apihelp-query+recentchanges-example-simple": "Список последних изменений.",
+ "apihelp-upload-example-url": "Загрузить через URL",
+ "api-help-main-header": "Главный модуль",
+ "api-help-parameters": "Параметр{{PLURAL:$1||ы}}:",
+ "api-help-param-deprecated": "Устаревший.",
+ "api-help-param-required": "Этот параметр является обязательным.",
+ "api-help-param-default": "По умолчанию: $1",
+ "api-help-param-default-empty": "По умолчанию: <span class=\"apihelp-empty\">(пусто)</span>",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(описание отсутствует)</span>",
+ "api-help-examples": "Пример{{PLURAL:$1||ы}}:",
+ "api-credits-header": "Создатели"
+}
diff --git a/includes/api/i18n/si.json b/includes/api/i18n/si.json
new file mode 100644
index 00000000..a075a49f
--- /dev/null
+++ b/includes/api/i18n/si.json
@@ -0,0 +1,70 @@
+{
+ "@metadata": {
+ "authors": [
+ "Susith Chandira Gts"
+ ]
+ },
+ "apihelp-main-param-action": "ඉටු කිරීමට ඇත්තේ කුමන ක්‍රියාවද.",
+ "apihelp-main-param-format": "ප්‍රතිදානයේ ආකෘතිය.",
+ "apihelp-main-param-requestid": "මෙහි ඇති සියලුම වටිනාකම් ප්‍රතිචාරයන්හි අන්තර්ගතකොට ඇත. ඇතැම් විට පැහැදිලිව වටහාගත් ඉල්ලීම් සදහා භාවිතා වේ.",
+ "apihelp-main-param-servedby": "ප්‍රතිපලයන්හි ඉල්ලීම් ඉටුකළ ධාරකනාමය ඇතුලත් කරන්න.",
+ "apihelp-main-param-curtimestamp": "ප්‍රථිපලයන්හි කාල මුද්‍රාව ඇතුලත් කරන්න.",
+ "apihelp-help-description": "නිරූපිත ඒකක සදහා උදවු පෙන්වන්න.",
+ "apihelp-help-param-submodules": "නම් කරන ලද ඒකකයේ, අනුඒකක සදහා උදවු ඇතුලත් කරන්න.",
+ "apihelp-help-param-helpformat": "උදවු ප්‍රතිදානයේ ආකෘතිය.",
+ "apihelp-help-param-wrap": "ප්‍රතිදානය නියමිත API අනුකූලතා ආකෘතියකට හරවන්න.",
+ "apihelp-help-param-toc": "HTML ප්‍රතිදනයන්ගේ පටුනේ ලැයිස්තුවක් ඇතුලත් කරන්න.",
+ "apihelp-help-example-main": "ප්‍රධාන ඒකකය සදහා උදවු කරන්න",
+ "apihelp-help-example-recursive": "සියලුම උදවු එක පිටුවක් තුල",
+ "apihelp-help-example-query": "සැකසහිත අනුඒකක සදහා උදවු කරන්න",
+ "apihelp-format-example-generic": "$1 ආකෘතියේ ඇති සැක සහිත ප්‍රථිපල පරිවර්තනය කරන්න",
+ "apihelp-dbg-description": "ප්‍රතිදාන දත්ත PHP හි var_export() ආකෘතියෙන් පවතී.",
+ "apihelp-dbgfm-description": "ප්‍රතිදාන දත්ත PHP හි var_export() ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "apihelp-dump-description": "ප්‍රතිදාන දත්ත PHP හි var_dump() ආකෘතියෙන් පවතී.",
+ "apihelp-dumpfm-description": "ප්‍රතිදාන දත්ත PHP හි var_dump() ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "apihelp-json-description": "ප්‍රතිදාන දත්ත JSON ආකෘතියෙන් පවතී.",
+ "apihelp-jsonfm-description": "ප්‍රතිදාන දත්ත JSON ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "apihelp-none-description": "ප්‍රතිදානයේ කිසිවක් නොමැත.",
+ "apihelp-php-description": "ප්‍රතිදාන දත්ත serialized PHP ආකෘතියෙන් පවතී.",
+ "apihelp-phpfm-description": "ප්‍රතිදාන දත්ත serialized PHP ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "apihelp-txt-description": "ප්‍රතිදාන දත්ත PHP හි print_r() ආකෘතියෙන් පවතී.",
+ "apihelp-txtfm-description": "ප්‍රතිදාන දත්ත PHP හි print_r() ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "apihelp-wddx-description": "ප්‍රතිදාන දත්ත WDDX ආකෘතියෙන් පවතී",
+ "apihelp-wddxfm-description": "ප්‍රතිදාන දත්ත WDDX ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "apihelp-xml-description": "ප්‍රතිදාන දත්ත XML ආකෘතියෙන් පවතී.",
+ "apihelp-xml-param-includexmlnamespace": "නිරූපණය කළා නම්, XML නාමාවකාශයක් එකතු කරන්න.",
+ "apihelp-xmlfm-description": "ප්‍රතිදාන දත්ත XML ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "apihelp-yaml-description": "ප්‍රතිදාන දත්ත YAML ආකෘතියෙන් පවතී.",
+ "apihelp-yamlfm-description": "ප්‍රතිදාන දත්ත YAML ආකෘතියෙන් පවතී (හොදම පිටපත HTML භාෂාවෙනි).",
+ "api-format-title": "මාධ්‍යවිකි API ප්‍රථිපල",
+ "api-help-title": "මාධ්‍යවිකි API උදවු",
+ "api-help-lead": "මෙය ස්වයං-ජනිත මාධ්‍යවිකි API \tප්‍රලේඛන පිටුවකි.\n\nප්‍රලේඛනය සහ උදාහරණ:\nhttps://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "ප්‍රධාන ආකෘතිය",
+ "api-help-flag-deprecated": "මෙම ආකෘතිය විරුද්ධත්වය ප්‍රකාශ කරන ලදී.",
+ "api-help-flag-internal": "<strong>මෙම ඒකකය අභ්‍යන්තර හෝ අස්ථායි.\n</strong> එහි ක්‍රියාකාරිත්වය දැනුම් දීමකින් තොරව වෙනස් වියහැක.",
+ "api-help-flag-readrights": "මෙම ඒකකය සදහා හිමිකම් කියවීම අවශ්‍ය වේ.",
+ "api-help-flag-writerights": "මෙම ඒකකය සදහා හිමිකම් ලිවීම අවශ්‍ය වේ.",
+ "api-help-flag-mustbeposted": "මෙම ඒකකය POST ඉල්ලීම් පමණක් බාරගනී.",
+ "api-help-flag-generator": "මෙම ආකෘතිය \tඋත්පාදකයක් ලෙස භාවිතා කල හැක.",
+ "api-help-parameters": "{{PLURAL:$1|පරාමිතිය|පරාමිතීන්}}:",
+ "api-help-param-deprecated": "විරුද්ධත්වය ප්‍රකාශ කර ඇත.",
+ "api-help-param-required": "මෙම පරාමිතිය අවශ්‍යයි.",
+ "api-help-param-list": "{{PLURAL:$1|1=එක් වටිනාකමක්|2=වටිනාකම් (\"{{!}}\" සමග වෙන් කරන්න)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=හිස් කල යුතුයි|හිස් කල හැකියි, හෝ $2}}",
+ "api-help-param-limit": "$1 ට වඩා අනුමත නොකරයි.",
+ "api-help-param-limit2": "$1 කට වැඩ අනුමත කරන්නේ නැත ($2 බොට්ස් සදහාය).",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=අගය|2=අගයන්}} $2 ට වඩා අඩු නොවිය යුතුය.",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=වටිනාකම|2=වටිනාකම්}} $3 ට ව වැඩි නොවිය යුතුය.",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=අගය|2=අගයන්}} $2 සහ $3 අතර පැවතිය යුතුය.",
+ "api-help-param-multi-separate": "වටිනාකම් \"|\" සමග වෙන් කරන්න.",
+ "api-help-param-multi-max": "අංක සදහා උපරිම වටිනාකම {{PLURAL:$1|$1}}\n({{PLURAL:$2|$2}} බොට්ස් සදහා)",
+ "api-help-param-default": "Default: $1",
+ "api-help-param-default-empty": "Default: <span class=\"apihelp-empty\">(හිස්)</span>",
+ "api-help-param-token": "[[Special:ApiHelp/query+tokens|action=query&meta=tokens]] මගින් \"$1\" \tසංඥාව සොයාගන්නා ලදී",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(විස්තරයක් නැත)</span>",
+ "api-help-examples": "{{PLURAL:$1|උදාහරණය|උදාහරණ}}:",
+ "api-help-permissions": "{{PLURAL:$1|අවසරය|අවසරයන්}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|\tප්‍රදානලාභියාට}}: $2",
+ "api-credits-header": "ස්තුතිය",
+ "api-credits": "API වැඩිදියුණු කරන්නන්:\n* Roan Kattouw (ප්‍රධානියා 2007 සැප්. –2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (නිර්මාපකයා, ප්‍රධානියා 2006 සැප්. – 2007 සැප්.)\n* Brad Jorsch (ප්‍රධානියා 2013–මේ දක්වා)\n\nඔබගේ අදහස්, යෝජනා හා ගැටළු mediawiki-api@lists.wikimedia.org වෙත යොමු කරන්න, පින්තූර හෝ ගොනු හරහා ගැටළු ඉදිරිපත් කිරීමට https://phabricator.wikimedia.org/ වෙත පිවිසෙන්න."
+}
diff --git a/includes/api/i18n/sr-ec.json b/includes/api/i18n/sr-ec.json
new file mode 100644
index 00000000..db801b8c
--- /dev/null
+++ b/includes/api/i18n/sr-ec.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "Milicevic01",
+ "Aktron"
+ ]
+ },
+ "apihelp-block-description": "Блокирај корисника.",
+ "apihelp-block-param-reason": "Разлог за блокирање.",
+ "apihelp-createaccount-param-name": "Корисничко име.",
+ "apihelp-delete-description": "Обриши страницу.",
+ "apihelp-edit-param-text": "Страница са садржајем.",
+ "apihelp-edit-param-minor": "Мања измена.",
+ "apihelp-edit-example-edit": "Уређивање странице.",
+ "apihelp-emailuser-description": "Слање е-поруке кориснику",
+ "apihelp-emailuser-param-target": "Корисник је послао е-поруку.",
+ "apihelp-feedcontributions-param-year": "Од године (и раније).",
+ "apihelp-filerevert-description": "Вратити датотеку у ранију верзију.",
+ "apihelp-help-example-recursive": "Сва помоћ у једној страници.",
+ "apihelp-login-param-name": "Корисничко име.",
+ "apihelp-login-param-password": "Лозинка.",
+ "apihelp-login-example-login": "Пријавa.",
+ "apihelp-move-description": "Премештање странице."
+}
diff --git a/includes/api/i18n/sr-el.json b/includes/api/i18n/sr-el.json
new file mode 100644
index 00000000..55611f08
--- /dev/null
+++ b/includes/api/i18n/sr-el.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Milicevic01"
+ ]
+ },
+ "apihelp-block-description": "Blokiraj korisnika.",
+ "apihelp-block-param-reason": "Razlog za blokiranje.",
+ "apihelp-delete-description": "Obriši stranicu.",
+ "apihelp-edit-param-minor": "Manja izmena."
+}
diff --git a/includes/api/i18n/sv.json b/includes/api/i18n/sv.json
new file mode 100644
index 00000000..aa88484f
--- /dev/null
+++ b/includes/api/i18n/sv.json
@@ -0,0 +1,372 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jopparn",
+ "Lokal Profil",
+ "WikiPhoenix",
+ "Victorsa",
+ "Albinomamba",
+ "Peki01",
+ "Stens51",
+ "Boom",
+ "Jenniesarina",
+ "Marfuas",
+ "VickyC"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Dokumentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api E-postlista]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API-aviseringar]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R|Buggar & förslag]\n</div>\n<strong>Status:</strong> Alla funktioner som visas på denna sida borde fungera. API:et är dock fortfarande under aktiv utveckling och kan ändras när som helst. Prenumerera på [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/mediawiki-api-announce e-postlistan] för att få aviseringar om uppdateringar.\n\n<strong>Felaktiga förfrågningar:</strong> När felaktiga förfrågningar skickas till API:et skickas en HTTP-header med nyckeln \"MediaWiki-API-Error\" och sedan sätts både värdet på headern och den felkoden som returneras till samma värde. För mer information läs [[mw:API:Errors_and_warnings|API: Fel och varningar]].",
+ "apihelp-main-param-action": "Vilken åtgärd som ska utföras.",
+ "apihelp-main-param-format": "Formatet för utdata.",
+ "apihelp-main-param-smaxage": "Ange headervärdet <code>s-maxage</code> till så här många sekunder. Fel cachelagras aldrig.",
+ "apihelp-main-param-maxage": "Ange headervärdet <code>max-age</code> till så här många sekunder. Fel cachelagras aldrig.",
+ "apihelp-main-param-assert": "Bekräftar att användaren är inloggad om satt till <kbd>user</kbd>, eller har bot-användarrättigheter om satt till <kbd>bot</kbd>.",
+ "apihelp-main-param-requestid": "Alla värde som anges här kommer att inkluderas i svaret. Kan användas för att särskilja förfrågningar.",
+ "apihelp-main-param-servedby": "Inkludera det värdnamn som besvarade förfrågan i resultatet.",
+ "apihelp-main-param-curtimestamp": "Inkludera den aktuella tidsstämpeln i resultatet.",
+ "apihelp-main-param-origin": "När API:et används genom en cross-domain AJAX-begäran (CORS), ange detta till den ursprungliga domänen. Detta måste inkluderas i alla pre-flight-begäran, och mpste därför vara en del av den begärda URI:n (inte i POST-datat). Detta måste överensstämma med en av källorna i headern <code>Origin</code> exakt, så den måste sättas till något i stil med <kbd>http://en.wikipedia.org</kbd> eller <kbd>https://meta.wikimedia.org</kbd>. Om denna parameter inte överensstämmer med headern <code>Origin</code>, returneras ett 403-svar. Om denna parameter överensstämmer med headern <code>Origin</code> och källan är vitlistad, sätts en <code>Access-Control-Allow-Origin</code>-header.",
+ "apihelp-main-param-uselang": "Språk som ska användas för meddelandeöversättningar. En lista med koder kan hämtas från <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> med <kbd>siprop=languages</kbd>, eller ange <kbd>user</kbd> för att använda den aktuella användarens språkpreferenser, eller ange <kbd>content</kbd> för att använda innehållsspråket.",
+ "apihelp-block-description": "Blockera en användare.",
+ "apihelp-block-param-user": "Användare, IP-adress eller IP-intervall du vill blockera.",
+ "apihelp-block-param-expiry": "Förfallotid. Kan vara Kan vara relativt (t.ex. <kbd>5 months</kbd> eller <kbd>2 weeks</kbd>) eller absolut (t.ex. <kbd>2014-09-18T12:34:56Z</kbd>). Om satt till <kbd>infinite</kbd>, <kbd>indefinite</kbd> eller <kbd>never</kbd>, kommer blockeringen aldrig att löpa ut.",
+ "apihelp-block-param-reason": "Orsak till blockering.",
+ "apihelp-block-param-anononly": "Blockera endast anonyma användare (t.ex. inaktivera anonyma redigeringar för denna IP-adress).",
+ "apihelp-block-param-nocreate": "Förhindra registrering av användarkonton.",
+ "apihelp-block-param-autoblock": "Blockera automatiskt den senast använda IP-adressen, och alla efterföljande IP-adresser de försöker logga in från.",
+ "apihelp-block-param-noemail": "Hindra användaren från att skicka e-post via wikin. (Kräver rättigheten <code>blockemail</code>).",
+ "apihelp-block-param-hidename": "Döljer användarnamnet från blockeringsloggen. (Kräver rättigheten <code>hideuser</code>).",
+ "apihelp-block-param-allowusertalk": "Låt användaren redigera sin egen diskussionssida (beror på <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>).",
+ "apihelp-block-param-reblock": "Skriv över befintlig blockering om användaren redan är blockerad.",
+ "apihelp-block-param-watchuser": "Bevaka användarens eller IP-adressens användarsida och diskussionssida",
+ "apihelp-block-example-ip-simple": "Blockera IP-adressen <kbd>192.0.2.5</kbd> i tre dagar med motivationen <kbd>First strike</kbd>",
+ "apihelp-block-example-user-complex": "Blockera användare <kbd>Vandal</kbd> på obegränsad tid med motivationen <kbd>Vandalism</kbd>, och förhindra kontoskapande och e-post.",
+ "apihelp-clearhasmsg-description": "Rensa <code>hasmsg</code>-flaggan för den aktuella användaren.",
+ "apihelp-clearhasmsg-example-1": "Rensa <code>hasmsg</code>-flaggan för den aktuella användaren",
+ "apihelp-compare-description": "Hämta skillnaden mellan två sidor.\n\nEtt versionsnummer, en sidtitel, eller ett sid-Id för både \"from\" och \"to\" måste skickas.",
+ "apihelp-compare-param-fromtitle": "Första titeln att jämföra.",
+ "apihelp-compare-param-fromid": "Första sid-ID att jämföra.",
+ "apihelp-compare-param-fromrev": "Första version att jämföra.",
+ "apihelp-compare-param-totitle": "Andra titeln att jämföra.",
+ "apihelp-compare-param-toid": "Andra sid-ID att jämföra.",
+ "apihelp-compare-param-torev": "Andra version att jämföra.",
+ "apihelp-compare-example-1": "Skapa en diff mellan version 1 och 2",
+ "apihelp-createaccount-description": "Skapa ett nytt användarkonto.",
+ "apihelp-createaccount-param-name": "Användarnamn.",
+ "apihelp-createaccount-param-password": "Lösenord (ignoreras om <var>$1mailpassword</var> angetts).",
+ "apihelp-createaccount-param-domain": "Domän för extern autentisering (frivillig).",
+ "apihelp-createaccount-param-token": "Nyckel för kontoskapande erhölls i första begäran.",
+ "apihelp-createaccount-param-email": "Användarens e-postadress (valfritt).",
+ "apihelp-createaccount-param-realname": "Användarens riktiga namn (valfritt).",
+ "apihelp-createaccount-param-mailpassword": "Om satt till ett värde, skickas ett slumpmässigt lösenord till användaren via e-post.",
+ "apihelp-createaccount-param-reason": "Valfri anledning för att skapa kontot för att läggas till i loggarna.",
+ "apihelp-createaccount-param-language": "Språkkod att använda som standard för användaren (valfri, standardvärdet är innehållsspråket).",
+ "apihelp-createaccount-example-pass": "Skapa användaren <kbd>testuser</kbd> med lösenordet <kbd>test123</kbd>",
+ "apihelp-createaccount-example-mail": "Skapa användaren <kbd>testmailuser</kbd> och skicka ett slumpgenererat lösenord via e-post",
+ "apihelp-delete-description": "Radera en sida.",
+ "apihelp-delete-param-title": "Titel på sidan du vill radera. Kan inte användas tillsammans med <var>$1pageid</var>.",
+ "apihelp-delete-param-pageid": "Sid-ID för sidan att radera. Kan inte användas tillsammans med <var>$1titel</var>.",
+ "apihelp-delete-param-reason": "Orsak till radering. Om orsak inte ges kommer en orsak att automatiskt genereras och användas.",
+ "apihelp-delete-param-watch": "Lägg till sidan i aktuell användares bevakningslista.",
+ "apihelp-delete-param-watchlist": "Lägg till eller ta bort sidan ovillkorligen från den aktuella användarens bevakningslista, använd inställningar eller ändra inte bevakning.",
+ "apihelp-delete-param-unwatch": "Ta bort sidan från aktuell användares bevakningslista.",
+ "apihelp-delete-param-oldimage": "Namnet på den gamla bilden att radera som tillhandahålls av [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]].",
+ "apihelp-delete-example-simple": "Radera <kbd>huvudsidan</kbd>.",
+ "apihelp-delete-example-reason": "Raderar <kbd>huvudsidan</kbd> med orsaken <kbd>Förbereder flyttning</kbd>.",
+ "apihelp-disabled-description": "Denna modul har inaktiverats.",
+ "apihelp-edit-description": "Skapa och redigera sidor.",
+ "apihelp-edit-param-title": "Titel på sidan du vill redigera. Kan inte användas tillsammans med <var>$1pageid</var>.",
+ "apihelp-edit-param-pageid": "Sid-ID för sidan du vill redigera. Kan inte användas tillsammans med <var>$1titel</var>.",
+ "apihelp-edit-param-section": "Avsnittsnummer. <kbd>0</kbd> för det översta avsnittet, <kbd>new</kbd> för ett nytt avsnitt.",
+ "apihelp-edit-param-sectiontitle": "Rubriken för ett nytt avsnitt.",
+ "apihelp-edit-param-text": "Sidans innehåll.",
+ "apihelp-edit-param-summary": "Redigeringssammanfattning. Även avsnittets rubrik när $1section=new och $1sectiontitle inte anges.",
+ "apihelp-edit-param-minor": "Mindre redigering.",
+ "apihelp-edit-param-notminor": "Icke-mindre redigering.",
+ "apihelp-edit-param-bot": "Markera denna redigering som robotredigering.",
+ "apihelp-edit-param-basetimestamp": "Tidsstämpel för grundversionen, används för att upptäcka redigeringskonflikter. Kan erhållas genom [[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]].",
+ "apihelp-edit-param-starttimestamp": "Tidsstämpel för när redigeringsprocessen började, används för att upptäcka redigeringskonflikter. Ett lämpligt värde kan erhållas via <var>[[Special:ApiHelp/main|curtimestamp]]</var> när redigeringsprocessen startas (t.ex. när sidans innehåll laddas för redigering).",
+ "apihelp-edit-param-recreate": "Ignorera felmeddelande om sidan har blivit raderad under tiden.",
+ "apihelp-edit-param-createonly": "Redigera inte sidan om den redan finns.",
+ "apihelp-edit-param-nocreate": "Kasta ett fel om sidan inte finns.",
+ "apihelp-edit-param-watch": "Lägg till sidan i den aktuella användarens bevakningslista.",
+ "apihelp-edit-param-unwatch": "Ta bort sidan från aktuell användares bevakningslista.",
+ "apihelp-edit-param-watchlist": "Lägg till eller ta bort sidan ovillkorligen från den aktuella användarens bevakningslista, använd inställningar eller ändra inte bevakning.",
+ "apihelp-edit-param-md5": "MD5-hash för $1text-parametern, eller $1prependtext- och $1appendtext-parametrarna sammanfogade.",
+ "apihelp-edit-param-prependtext": "Lägg till denna text i början på sidan. Ersätter $1text.",
+ "apihelp-edit-param-appendtext": "Lägg till denna text i slutet på sidan. Ersätter $1text.\n\nAnvänd $1section=new för att lägga till en ny sektion, hellre än denna parameter.",
+ "apihelp-edit-param-redirect": "Åtgärda automatiskt omdirigeringar.",
+ "apihelp-edit-param-contentformat": "Det serialiseringsformat som används för indatatexten.",
+ "apihelp-edit-param-contentmodel": "Det nya innehållets innehållsmodell.",
+ "apihelp-edit-param-token": "Token ska alltid skickas som sista parameter, eller åtminstone efter $1text-parametern",
+ "apihelp-edit-example-edit": "Redigera en sida",
+ "apihelp-emailuser-description": "Skicka e-post till en användare.",
+ "apihelp-emailuser-param-target": "Användare att skicka e-post till.",
+ "apihelp-emailuser-param-subject": "Ämnesrubrik.",
+ "apihelp-emailuser-param-text": "E-postmeddelandets innehåll.",
+ "apihelp-emailuser-param-ccme": "Skicka en kopia av detta e-postmeddelande till mig.",
+ "apihelp-emailuser-example-email": "Skicka ett e-postmeddelande till användaren <kbd>WikiSysop</kbd> med texten <kbd>Content</kbd>.",
+ "apihelp-expandtemplates-description": "Expanderar alla mallar i wikitext.",
+ "apihelp-expandtemplates-param-title": "Sidans rubrik.",
+ "apihelp-expandtemplates-param-text": "Wikitext att konvertera.",
+ "apihelp-expandtemplates-param-revid": "Revision ID, för <nowiki>{{REVISIONID}}</nowiki> och liknande variabler.",
+ "apihelp-expandtemplates-param-includecomments": "Om HTML-kommentarer skall inkluderas i utdata.",
+ "apihelp-expandtemplates-param-generatexml": "Generera ett XML tolknings träd (ersatt av $1prop=parsetree).",
+ "apihelp-expandtemplates-example-simple": "Expandera wikitexten <kbd><nowiki>{{Projekt:Sandbox}}</nowiki></kbd>.",
+ "apihelp-feedcontributions-description": "Returnerar en användares bidragsflöde.",
+ "apihelp-feedcontributions-param-feedformat": "Flödets format.",
+ "apihelp-feedcontributions-param-user": "De användare vars bidrag ska hämtas.",
+ "apihelp-feedcontributions-param-namespace": "Vilken namnrymd att filtrera bidrag med.",
+ "apihelp-feedcontributions-param-year": "Från år (och tidigare).",
+ "apihelp-feedcontributions-param-month": "Från månad (och tidigare).",
+ "apihelp-feedcontributions-param-tagfilter": "Filtrera bidrag som har dessa taggar.",
+ "apihelp-feedcontributions-param-deletedonly": "Visa bara borttagna bidrag.",
+ "apihelp-feedcontributions-param-toponly": "Visa endast ändringar som är senaste revideringen.",
+ "apihelp-feedcontributions-param-newonly": "Visa endast redigeringar där sidor skapas.",
+ "apihelp-feedcontributions-param-showsizediff": "Visa skillnaden i storlek mellan revisioner.",
+ "apihelp-feedcontributions-example-simple": "Returnera bidrag för <kbd>Exempel</kbd>",
+ "apihelp-feedrecentchanges-description": "Returnerar ett flöde med senaste ändringar.",
+ "apihelp-feedrecentchanges-param-feedformat": "Flödets format.",
+ "apihelp-feedrecentchanges-param-namespace": "Namnrymder att begränsa resultaten till.",
+ "apihelp-feedrecentchanges-param-invert": "Alla namnrymder utom den valda.",
+ "apihelp-feedrecentchanges-param-days": "Dagar att begränsa resultaten till.",
+ "apihelp-feedrecentchanges-param-limit": "Maximalt antal resultat att returnera.",
+ "apihelp-feedrecentchanges-param-from": "Visa förändringar sedan dess.",
+ "apihelp-feedrecentchanges-param-hideminor": "Dölj mindre ändringar.",
+ "apihelp-feedrecentchanges-param-hidebots": "Dölj robotändringar.",
+ "apihelp-feedrecentchanges-param-hideanons": "Dölj ändringar av oinloggade användare.",
+ "apihelp-feedrecentchanges-param-hideliu": "Dölj ändringar av inloggade användare.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Dölj patrullerade ändringar.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Dölj ändringar av aktuell användare.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filtrera efter tagg.",
+ "apihelp-feedrecentchanges-param-target": "Visa endast ändringarna av sidor som den här sidan länkar till.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Visa ändringarna på sidor som är länkade till den valda sidan i stället.",
+ "apihelp-feedrecentchanges-example-simple": "Visa senaste ändringar",
+ "apihelp-feedrecentchanges-example-30days": "Visa senaste ändringar för 30 dygn",
+ "apihelp-feedwatchlist-description": "Returnerar ett flöde från bevakningslistan.",
+ "apihelp-feedwatchlist-param-feedformat": "Flödets format.",
+ "apihelp-feedwatchlist-param-hours": "Lista sidor ändrade inom så här många timmar från nu.",
+ "apihelp-feedwatchlist-param-linktosections": "Länka direkt till ändrade avsnitt om möjligt.",
+ "apihelp-feedwatchlist-example-default": "Visa flödet från bevakningslistan.",
+ "apihelp-feedwatchlist-example-all6hrs": "Visa alla ändringar på besökta sidor under de senaste sex timmarna.",
+ "apihelp-filerevert-description": "Återställ en fil till en äldre version.",
+ "apihelp-filerevert-param-filename": "Målfilens namn, utan prefixet Fil:.",
+ "apihelp-filerevert-param-comment": "Ladda upp kommentar.",
+ "apihelp-filerevert-param-archivename": "Arkiv-namn för revisionen att gå tillbaka till.",
+ "apihelp-filerevert-example-revert": "Återställ <kbd>Wiki.png</kbd> till versionen från <kbd>2011-03-05T15:27:40Z</kbd>",
+ "apihelp-help-description": "Visa hjälp för de angivna modulerna.",
+ "apihelp-help-param-modules": "Vilka moduler som hjälpen ska visas för (värdena på parametrarna <var>action</var> och <var>format</var>, eller <kbd>main</kbd>). Undermoduler kan anges med ett plustecken (<kbd>+</kbd>).",
+ "apihelp-help-param-submodules": "Inkludera hjälp för undermoduler av den namngivna modulen.",
+ "apihelp-help-param-recursivesubmodules": "Inkludera hjälp för undermoduler rekursivt.",
+ "apihelp-help-param-helpformat": "Formatet för hjälp-utdata.",
+ "apihelp-help-param-wrap": "Omge utdatan i en standard API respons struktur.",
+ "apihelp-help-param-toc": "Inkludera en innehållsförteckning i HTML-utdata.",
+ "apihelp-help-example-main": "Hjälp för huvudmodul",
+ "apihelp-help-example-recursive": "All hjälp på en sida",
+ "apihelp-help-example-help": "Hjälp för själva hjälpmodulen",
+ "apihelp-imagerotate-description": "Rotera en eller flera bilder.",
+ "apihelp-imagerotate-param-rotation": "Grader att rotera bild medurs.",
+ "apihelp-imagerotate-example-simple": "Rotera <kbd>File:Example.png</kbd> med <kbd>90</kbd> grader",
+ "apihelp-imagerotate-example-generator": "Rotera alla bilder i <kbd>Category:Flip</kbd> med <kbd>180</kbd> grader.",
+ "apihelp-import-description": "Importera en sida från en annan wiki, eller en XML fil. \n\nNotera att HTTP POST måste bli gjord som en fil uppladdning (d.v.s med multipart/form-data) när man skickar en fil för <var>xml</var> parametern.",
+ "apihelp-import-param-summary": "Importera sammanfattning.",
+ "apihelp-import-param-xml": "Uppladdad XML-fil.",
+ "apihelp-import-param-interwikisource": "För interwiki-importer: wiki som du vill importera från.",
+ "apihelp-import-param-interwikipage": "För interwiki-importer: sidan som du vill importera.",
+ "apihelp-import-param-fullhistory": "För interwiki-importer: importera hela historiken, inte bara den aktuella versionen.",
+ "apihelp-import-param-templates": "För interwiki-importer: importera även alla mallar som ingår.",
+ "apihelp-import-param-namespace": "För interwiki-importer: importera till denna namnrymd.",
+ "apihelp-import-param-rootpage": "Importera som undersida till denna sida.",
+ "apihelp-import-example-import": "Importera [[meta:Help:Parserfunktioner]] till namnrymd 100 med full historik.",
+ "apihelp-login-description": "Logga in och hämta autentiserings-cookies.\n\nOm inloggningen lyckas, finns de cookies som krävs med i HTTP-svarshuvuden. Om inloggningen misslyckas kan ytterligare försök per tidsenhet begränsas, som ett sätt att försöka minska risken för automatiserade lösenordsgissningar.",
+ "apihelp-login-param-name": "Användarnamn.",
+ "apihelp-login-param-password": "Lösenord.",
+ "apihelp-login-param-domain": "Domän (valfritt).",
+ "apihelp-login-param-token": "Login nyckel erhållen i första begäran.",
+ "apihelp-login-example-gettoken": "Hämta en login nyckel.",
+ "apihelp-login-example-login": "Logga in",
+ "apihelp-logout-description": "Logga ut och rensa sessionsdata.",
+ "apihelp-logout-example-logout": "Logga ut den aktuella användaren",
+ "apihelp-managetags-description": "Utför hanterings uppgifter relaterade till förändrings taggar.",
+ "apihelp-managetags-param-tag": "Tagg för att skapa, radera, aktivera eller inaktivera. Vid skapande av tagg kan taggen inte existera. Vid raderande av tagg måste taggen existera. För aktiverande av tagg måste taggen existera och inte användas i ett tillägg. För inaktivering av tagg måste taggen användas just nu och vara manuellt definierad.",
+ "apihelp-managetags-param-reason": "En icke-obligatorisk orsak för att skapa, radera, aktivera, eller inaktivera taggen.",
+ "apihelp-managetags-param-ignorewarnings": "Om du vill ignorera varningar som utfärdas under operationen.",
+ "apihelp-managetags-example-create": "Skapa en tagg vid namn <kbd>spam</kbd> med anledningen: <kbd>För användning i redigerings patrullering</kbd>",
+ "apihelp-managetags-example-delete": "Radera <kbd>vandalims</kbd> taggen med andledningen: <kbd>Felstavat</kbd>",
+ "apihelp-managetags-example-activate": "Aktivera en tagg med namn <kbd>spam</kbd> med anledningen: <kbd>För användning i redigerings patrullering</kbd>",
+ "apihelp-managetags-example-deactivate": "Inaktivera en tagg vid namn <kbd>spam</kbd> med anledningen: <kbd>Inte längre behövd</kbd>",
+ "apihelp-move-description": "Flytta en sida.",
+ "apihelp-move-param-from": "Titeln på sidan du vill flytta. Kan inte användas tillsammans med <var>$1fromid</var>.",
+ "apihelp-move-param-fromid": "Sid-ID för sidan att byta namn. Kan inte användas tillsammans med <var>$1from</var>.",
+ "apihelp-move-param-to": "Titel att byta namn på sidan till.",
+ "apihelp-move-param-reason": "Orsak till namnbytet.",
+ "apihelp-move-param-movetalk": "Byt namn på diskussionssidan, om den finns.",
+ "apihelp-move-param-movesubpages": "Byt namn på undersidor, om tillämpligt.",
+ "apihelp-move-param-noredirect": "Skapa inte en omdirigering.",
+ "apihelp-move-param-watch": "Lägg till sidan och omdirigeringen till den aktuella användarens bevakningslista.",
+ "apihelp-move-param-unwatch": "Ta bort sidan och omdirigeringen från den aktuella användarens bevakningslista.",
+ "apihelp-move-param-watchlist": "Lägg till eller ta bort sidan ovillkorligen från den aktuella användarens bevakningslista, använd inställningar eller ändra inte bevakning.",
+ "apihelp-move-param-ignorewarnings": "Ignorera alla varningar.",
+ "apihelp-move-example-move": "Flytta <kbd>Felaktig titel</kbd> till <kbd>Korrekt titel</kbd> utan att lämna en omdirigering.",
+ "apihelp-opensearch-description": "Sök wikin med protokollet OpenSearch.",
+ "apihelp-opensearch-param-search": "Söksträng.",
+ "apihelp-opensearch-param-limit": "Maximalt antal resultat att returnera.",
+ "apihelp-opensearch-param-namespace": "Namnrymder att genomsöka.",
+ "apihelp-opensearch-param-suggest": "Gör ingenting om <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> är falskt.",
+ "apihelp-opensearch-param-format": "Formatet för utdata.",
+ "apihelp-opensearch-example-te": "Hitta sidor som börjar med <kbd>Te</kbd>.",
+ "apihelp-options-param-reset": "Återställer inställningarna till sidans standardvärden.",
+ "apihelp-options-example-reset": "Återställ alla inställningar",
+ "apihelp-options-example-complex": "Återställ alla inställningar, ställ sedan in <kbd>skin</kbd> och <kbd>nickname</kbd>.",
+ "apihelp-paraminfo-description": "Få information om API moduler.",
+ "apihelp-paraminfo-param-helpformat": "Format för hjälpsträngar.",
+ "apihelp-paraminfo-param-mainmodule": "Få information om huvud-modulen (top-level) också. Använd <kbd>$1modules=main</kbd> istället.",
+ "apihelp-parse-param-summary": "Sammanfattning att tolka.",
+ "apihelp-parse-param-page": "Tolka innehållet av denna sida. Kan inte användas tillsammans med <var>$1text</var> och <var>$1title</var>.",
+ "apihelp-parse-param-pageid": "Tolka innehållet på denna sida. Åsidosätter <var>$1sidan</var>.",
+ "apihelp-parse-param-preview": "Tolka i preview-läget.",
+ "apihelp-parse-example-page": "Tolka en sida.",
+ "apihelp-parse-example-text": "Tolka wikitext.",
+ "apihelp-parse-example-texttitle": "Tolka wikitext, specificera sid-titeln.",
+ "apihelp-parse-example-summary": "Tolka en sammanfattning.",
+ "apihelp-patrol-description": "Patrullera en sida eller en version.",
+ "apihelp-patrol-param-revid": "Versions ID att patrullera.",
+ "apihelp-patrol-example-rcid": "Patrullera en nykommen ändring.",
+ "apihelp-patrol-example-revid": "Patrullera en sidversion",
+ "apihelp-protect-description": "Ändra skyddsnivån för en sida.",
+ "apihelp-protect-example-protect": "Skydda en sida",
+ "apihelp-query-param-list": "Vilka listor att hämta.",
+ "apihelp-query-param-meta": "Vilka metadata att hämta.",
+ "apihelp-query+allcategories-param-dir": "Riktning att sortera mot.",
+ "apihelp-query+allcategories-param-min": "Returnera endast kategorier med minst så här många medlemmar.",
+ "apihelp-query+allcategories-param-max": "Returnera endast kategorier med som mest så här många medlemmar.",
+ "apihelp-query+allcategories-param-limit": "Hur många kategorier att returnera.",
+ "apihelp-query+alldeletedrevisions-description": "Lista alla raderade revisioner av en användare or inom en namnrymd.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "Kan endast användas med <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "Kan inte användas med <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-param-from": "Börja lista vid denna titel.",
+ "apihelp-query+alldeletedrevisions-param-to": "Sluta lista vid denna titel.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Sök alla sid-titlar som börjar med detta värde.",
+ "apihelp-query+alldeletedrevisions-param-tag": "Lista bara revideringar taggade med denna tagg.",
+ "apihelp-query+alldeletedrevisions-param-user": "Lista bara revideringar av denna användaren.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "Lista inte revideringar av denna användaren.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Lista bara sidor i denna namnrymd.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Lista dem första 50 revideringarna i huvud-namnrymden",
+ "apihelp-query+allfileusages-description": "Lista all fil användningsområden, inklusive icke-existerande.",
+ "apihelp-query+allfileusages-param-prefix": "Sök för all fil-titlar som börjar med detta värde.",
+ "apihelp-query+allfileusages-param-limit": "Hur många saker att returnera totalt.",
+ "apihelp-query+allfileusages-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+allfileusages-example-unique": "Lista unika filtitlar",
+ "apihelp-query+allfileusages-example-unique-generator": "Hämtar alla fil titlar, markerar dem saknade.",
+ "apihelp-query+allfileusages-example-generator": "Hämtar sidor som innehåller filerna.",
+ "apihelp-query+allimages-param-sort": "Egenskap att sortera efter.",
+ "apihelp-query+allimages-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+allimages-param-prefix": "Sök för alla bild titlar som börjar med detta värde. Kan endast användas med $1sort=name.",
+ "apihelp-query+allimages-param-minsize": "Begränsning på bilder med åtminstone så här många bytes.",
+ "apihelp-query+allimages-param-maxsize": "Begränsning på bilder med som mest så här många bytes.",
+ "apihelp-query+allimages-param-sha1": "SHA1 hash av bild. Åsidosätter $1sha1base36.",
+ "apihelp-query+allimages-param-sha1base36": "SHA1 hash av bild i bas 36 (används i MediaWiki).",
+ "apihelp-query+allimages-param-user": "Returnera enbart filer uppladdade av denna användare. Kan enbart användas med $1sort=timestamp. Kan inte användas tillsammans med $1filterbots.",
+ "apihelp-query+allimages-param-filterbots": "Hur man filtrerar filer uppladdade av bots. Kan enbart användas med $1sort=timestamp. Kan inte användas tillsammans med $1user.",
+ "apihelp-query+allimages-param-mime": "Vilka MIME-typer du vill söka efter, t.ex. <kbd>image/jpeg</kbd>.",
+ "apihelp-query+allimages-param-limit": "Hur många bilder att returnera totalt.",
+ "apihelp-query+allimages-example-B": "Visa en lista över filer som börjar på bokstaven <kbd>B</kbd>.",
+ "apihelp-query+allimages-example-recent": "Visa en lista över nyligen överförda filer, ungefär som [[Special:Nya_filer]].",
+ "apihelp-query+allimages-example-mimetypes": "Visa en lista över filer med MIME-typen <kbd>image/png</kbd> eller <kbd>image/gif</kbd>",
+ "apihelp-query+allimages-example-generator": "Visa info om 4 filer som börjar med bokstaven <kbd>T</kbd>.",
+ "apihelp-query+alllinks-param-prefix": "Sök alla länkade titlar som börjar med detta värde.",
+ "apihelp-query+alllinks-param-limit": "Hur många saker att returnera totalt.",
+ "apihelp-query+alllinks-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+alllinks-example-B": "Lista länkade titlar, inkluderade saknade, med dem sid-IDs dem är från, med början vid <kbd>B</kbd>.",
+ "apihelp-query+alllinks-example-unique": "Lista unika länkade titlar.",
+ "apihelp-query+alllinks-example-unique-generator": "Hämtar alla länkade titlar, markera de saknade.",
+ "apihelp-query+alllinks-example-generator": "Hämtar sidor som innehåller länkarna.",
+ "apihelp-query+allmessages-description": "Returnera meddelande från denna sida.",
+ "apihelp-query+allmessages-param-messages": "Vilka meddelande att ge som utdata. <kbd>*</kbd> (standard) betyder alla meddelande .",
+ "apihelp-query+allmessages-param-prop": "Vilka egenskaper att hämta.",
+ "apihelp-query+allmessages-param-args": "Argument som ska substitueras i meddelandet.",
+ "apihelp-query+allmessages-param-filter": "Returnera enbart meddelande med namn som innehåller denna sträng.",
+ "apihelp-query+allmessages-param-customised": "Returnera endast meddelanden i detta anpassningstillstånd.",
+ "apihelp-query+allmessages-param-lang": "Returnera meddelanden på detta språk.",
+ "apihelp-query+allmessages-param-from": "Returnera meddelanden med början på detta meddelande.",
+ "apihelp-query+allmessages-param-to": "Returnera meddelanden fram till och med detta meddelande.",
+ "apihelp-query+allmessages-param-title": "Sidnamn som ska användas som kontext vid parsning av meddelande (för alternativet $1enableparser).",
+ "apihelp-query+allmessages-param-prefix": "Returnera meddelanden med detta prefix.",
+ "apihelp-query+allmessages-example-ipb": "Visa meddelanden som börjar med <kbd>ipb-</kbd>.",
+ "apihelp-query+allmessages-example-de": "Visa meddelanden <kbd>august</kbd> och <kbd>mainpage</kbd> på tyska.",
+ "apihelp-query+allpages-param-prefix": "Sök efter alla sidtitlar som börjar med detta värde.",
+ "apihelp-query+allpages-param-filterredir": "Vilka sidor att lista.",
+ "apihelp-query+allpages-param-minsize": "Begränsa till sidor med detta antal byte eller fler.",
+ "apihelp-query+allpages-param-maxsize": "Begränsa till sidor med högst så här många byte.",
+ "apihelp-query+allpages-param-prtype": "Begränsa till endast skyddade sidor.",
+ "apihelp-query+allpages-param-limit": "Hur många sidor att returnera totalt.",
+ "apihelp-query+allpages-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+allpages-example-B": "Visa en lista över sidor som börjar på bokstaven <kbd>B</kbd>.",
+ "apihelp-query+allredirects-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+allredirects-example-unique-generator": "Hämtar alla målsidor, markerar de som saknas.",
+ "apihelp-query+alltransclusions-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+allusers-param-prefix": "Sök för alla användare som börjar med detta värde.",
+ "apihelp-query+allusers-param-dir": "Riktning att sortera i.",
+ "apihelp-query+allusers-param-group": "Inkludera bara användare i de givna grupperna.",
+ "apihelp-query+allusers-param-excludegroup": "Exkludera användare i de givna grupperna.",
+ "apihelp-query+allusers-param-rights": "Inkludera bara användare med de givna rättigheterna. Inkluderar inte rättigheter givna med implicita eller automatiskt promotade grupper som *, användare, eller auto-konfirmerad.",
+ "apihelp-query+allusers-param-limit": "Hur många användarnamn att returnera totalt.",
+ "apihelp-query+allusers-param-witheditsonly": "Lista bara användare som har gjort redigeringar.",
+ "apihelp-query+allusers-param-activeusers": "Lista bara användare aktiva i dem sista $1{{PLURAL:$1|dagen|dagarna}}.",
+ "apihelp-query+allusers-example-Y": "Lista användare som börjar på <kbd>Y</kbd>.",
+ "apihelp-query+backlinks-description": "Hitta alla sidor som länkar till den givna sidan.",
+ "apihelp-query+backlinks-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+categories-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+duplicatefiles-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+duplicatefiles-example-generated": "Leta efter kopior av alla filer.",
+ "apihelp-query+embeddedin-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+embeddedin-param-limit": "Hur många sidor att returnera totalt.",
+ "apihelp-query+filearchive-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+filearchive-example-simple": "Visa en lista över alla borttagna filer.",
+ "apihelp-query+images-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+imageusage-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+imageusage-example-simple": "Visa sidor med hjälp av [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+imageusage-example-generator": "Hämta information om sidor med hjälp av [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+iwbacklinks-param-limit": "Hur många sidor att returnera totalt.",
+ "apihelp-query+iwbacklinks-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+iwlinks-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+langbacklinks-param-limit": "Hur många sidor att returnera totalt.",
+ "apihelp-query+langbacklinks-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+langbacklinks-example-simple": "Hämta sidor som länkar till [[:fr:Test]].",
+ "apihelp-query+langlinks-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+links-param-dir": "Riktningen att lista mot.",
+ "apihelp-query+protectedtitles-param-limit": "Hur många sidor att returnera totalt.",
+ "apihelp-query+protectedtitles-example-simple": "Lista skyddade titlar.",
+ "apihelp-query+recentchanges-example-simple": "Lista de senaste ändringarna.",
+ "apihelp-query+revisions-example-first5-not-localhost": "Hämta första 5 revideringarna av \"huvudsidan\" och som inte gjorts av anonym användare \"127.0.0.1\"",
+ "apihelp-query+siteinfo-example-simple": "Hämta information om webbplatsen.",
+ "apihelp-query+stashimageinfo-description": "Returnerar filinformation för temporära filer.",
+ "apihelp-query+stashimageinfo-param-filekey": "Nyckel som identifierar en tidigare uppladdning som lagrats temporärt.",
+ "apihelp-query+stashimageinfo-example-simple": "Returnerar information för en temporär fil",
+ "apihelp-query+tags-example-simple": "Lista tillgängliga taggar.",
+ "apihelp-query+userinfo-example-simple": "Få information om den aktuella användaren.",
+ "apihelp-query+userinfo-example-data": "Få ytterligare information om den aktuella användaren.",
+ "apihelp-query+watchlist-description": "Hämta de senaste ändringarna på sidor i den nuvarande användarens bevakningslista.",
+ "apihelp-query+watchlist-example-allrev": "Hämta information om de senaste ändringarna på sidor på den aktuella användarens bevakningslista.",
+ "apihelp-query+watchlist-example-generator": "Hämta sidinformation för nyligen uppdaterade sidor på nuvarande användares bevakningslista.",
+ "apihelp-query+watchlist-example-generator-rev": "Hämta ändringsinformation för nyligen uppdaterade sidor på nuvarande användares bevakningslista.",
+ "apihelp-query+watchlistraw-description": "Hämta alla sidor på den aktuella användarens bevakningslista.",
+ "apihelp-query+watchlistraw-example-simple": "Lista sidor på den aktuella användarens bevakningslista.",
+ "apihelp-setnotificationtimestamp-example-all": "Återställ meddelandestatus för hela bevakningslistan.",
+ "apihelp-upload-param-filekey": "Nyckel som identifierar en tidigare uppladdning som lagrats temporärt.",
+ "apihelp-upload-param-stash": "Om angiven, kommer servern att temporärt lagra filen istället för att lägga till den i centralförvaret.",
+ "apihelp-upload-example-url": "Ladda upp från URL.",
+ "apihelp-upload-example-filekey": "Slutför en uppladdning som misslyckades på grund av varningar.",
+ "api-help-main-header": "Huvudmodul",
+ "api-help-flag-deprecated": "Denna modul är föråldrad.",
+ "api-help-flag-internal": "<strong>Denna modul är intern eller instabil.</strong> Dess funktion kan ändras utan föregående meddelande.",
+ "api-help-flag-readrights": "Denna modul kräver läsrättigheter.",
+ "api-help-flag-writerights": "Denna modul kräver skrivrättigheter.",
+ "api-help-flag-mustbeposted": "Denna modul accepterar endast POST-begäranden.",
+ "api-help-flag-generator": "Denna modul kan användas som en generator.",
+ "api-help-parameters": "{{PLURAL:$1|Parameter|Parametrar}}:",
+ "api-help-param-deprecated": "Föråldrad.",
+ "api-help-param-required": "Denna parameter är obligatorisk.",
+ "api-help-param-list": "{{PLURAL:$1|1=ett värde|2=värden (separade med \"{{!}}\")}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Måste vara tom|Kan vara tom, eller $2}}",
+ "api-help-param-limit": "Inte mer än $1 tillåts.",
+ "api-help-param-limit2": "Inte mer än $1 ($2 för robotar) tillåts."
+}
diff --git a/includes/api/i18n/te.json b/includes/api/i18n/te.json
new file mode 100644
index 00000000..8678f2a2
--- /dev/null
+++ b/includes/api/i18n/te.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Veeven"
+ ]
+ },
+ "apihelp-feedrecentchanges-example-simple": "ఇటీవలి మార్పులను చూడండి"
+}
diff --git a/includes/api/i18n/tl.json b/includes/api/i18n/tl.json
new file mode 100644
index 00000000..ddf52d40
--- /dev/null
+++ b/includes/api/i18n/tl.json
@@ -0,0 +1,35 @@
+{
+ "@metadata": {
+ "authors": [
+ "Leeheonjin"
+ ]
+ },
+ "apihelp-feedrecentchanges-example-simple": "Ipakit ang mga kamakailangang pagbabago.",
+ "apihelp-feedrecentchanges-example-30days": "Ipakita ang mga huling pagbabago sa loob para sa 30 araw.",
+ "apihelp-help-example-main": "Tulong para sa pangunahing modulo.",
+ "apihelp-help-example-recursive": "Lahat ng tulong sa iisang pahina.",
+ "apihelp-login-example-login": "Lumagda (Mag-log in).",
+ "apihelp-move-example-move": "I-urong ang <kbd>Badtitle</kbd> sa <kbd>Goodtitle</kbd> nang hindi nag-iiwan ng redirekta.",
+ "apihelp-options-example-reset": "Ibalik sa dati ang lahat ng mga kanaisan.",
+ "apihelp-patrol-example-rcid": "Bantayan ang kasalukuyang pagbabago.",
+ "apihelp-query+allimages-example-B": "Ipakita ang talaan ng mga talakasang nagsisimula sa titik <kbd>B</kbd>.",
+ "apihelp-query+alllinks-example-generator": "Kinukuha ang mga pahinang naglalaman ng mga kawing.",
+ "apihelp-query+allpages-example-B": "Ipakita ang talaan ng mga pahinang nagsisimula sa titik <kbd>B</kbd>.",
+ "apihelp-query+alltransclusions-example-generator": "Kinukuha ang mga pahinang naglalaman ng mga transklusyon.",
+ "apihelp-query+backlinks-example-simple": "Ipakita ang mga kawing sa <kbd>Unang pahina<kbd>.",
+ "apihelp-query+categoryinfo-example-simple": "Kumuha ng impormasyon tungkol sa <kbd>Kategorya:Foo</kbd> at <kbd>Kategorya:Bar</kbd>.",
+ "apihelp-query+duplicatefiles-example-simple": "Maghanap para sa mga duplika ng [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+duplicatefiles-example-generated": "Hanapin ang mga duplika ng lahat ng talakasan.",
+ "apihelp-query+images-example-simple": "Kumuha ng talaan ng mga talakasang ginagamit sa [[Unang Pahina]].",
+ "apihelp-query+imageusage-example-simple": "Ipakita ang mga pahina gamit ang [[:File:Albert Einstein Head.jpg]].",
+ "apihelp-query+iwbacklinks-example-simple": "Kumuha ng mga pahinang nakarugtong sa [[wikibooks:Test]].",
+ "apihelp-query+linkshere-example-generator": "Kumuha ng kabatiran tungkol sa mga pahina na kumakawing sa [[Unang Pahina]].",
+ "apihelp-query+recentchanges-example-simple": "Talaan ng mga kamakailang pagbabago.",
+ "apihelp-query+search-example-text": "Hanapin ang mga teksto para sa <kbd>kahulugan</kbd>.",
+ "apihelp-query+siteinfo-example-simple": "Kunin ang impormasyon ng sityo.",
+ "apihelp-query+templates-example-simple": "Kumuha ng mga suleras o padron na ginamit sa pahinang <kbd>Unang Pahina</kbd>.",
+ "apihelp-query+watchlist-example-simple": "Itala ang mga punong pagbabago ng mga kasalukuyang binagong pahina sa kasalukuyang listahan ng binabantayan ng tagagamit.",
+ "apihelp-revisiondelete-example-revision": "Itago ang nilalaman para sa pagbabago ng <kbd>12345</kbd> sa pahinang <kbd>Unang Pahina</kbd>.",
+ "apihelp-upload-example-url": "Mag-karga mula sa URL.",
+ "apihelp-watch-example-watch": "Bantayan ang pahinang <kbd>Unang Pahina</kbd>."
+}
diff --git a/includes/api/i18n/tr.json b/includes/api/i18n/tr.json
new file mode 100644
index 00000000..3a9ff258
--- /dev/null
+++ b/includes/api/i18n/tr.json
@@ -0,0 +1,40 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sayginer"
+ ]
+ },
+ "apihelp-edit-param-text": "Sayfa içeriği.",
+ "apihelp-edit-param-minor": "Küçük değişiklik.",
+ "apihelp-edit-param-nocreate": "Sayfa mevcut değilse hata oluştur.",
+ "apihelp-edit-param-watch": "Sayfayı izleme listenize ekleyin.",
+ "apihelp-edit-param-unwatch": "Sayfayı izleme listenizden çıkarın.",
+ "apihelp-edit-param-redirect": "Yönlendirmeleri otomatik olarak çöz.",
+ "apihelp-emailuser-description": "Bir kullanıcıya e-posta gönder.",
+ "apihelp-emailuser-param-target": "E-posta gönderilecek kullanıcı.",
+ "apihelp-emailuser-param-subject": "Konu başlığı.",
+ "apihelp-emailuser-param-text": "E-posta metni.",
+ "apihelp-emailuser-param-ccme": "Bu e-postanın bir kopyasını bana gönder.",
+ "apihelp-feedcontributions-param-toponly": "Yalnızca son revizyon olan değişiklikleri göster.",
+ "apihelp-feedcontributions-param-newonly": "Yalnızca yeni sayfa oluşturan değişiklikleri göster.",
+ "apihelp-feedcontributions-param-showsizediff": "Sürümler arasındaki boyut farkını göster.",
+ "apihelp-feedrecentchanges-param-limit": "Verilecek azami sonuç sayısı.",
+ "apihelp-feedrecentchanges-param-hideminor": "Küçük değişiklikleri gizle.",
+ "apihelp-feedrecentchanges-param-hidebots": "Bot değişikliklerini gizle.",
+ "apihelp-feedrecentchanges-param-hideanons": "Anonim kullanıcı değişikliklerini gizle.",
+ "apihelp-feedrecentchanges-param-hideliu": "Kayıtlı kullanıcı değişikliklerini gizle.",
+ "apihelp-feedrecentchanges-param-hidemyself": "Kendi değişikliklerini gizle.",
+ "apihelp-feedrecentchanges-example-simple": "Son değişiklikleri göster",
+ "apihelp-feedrecentchanges-example-30days": "Son 30 gündeki değişiklikleri göster",
+ "apihelp-filerevert-description": "Bir dosyayı eski bir sürümüne geri döndür.",
+ "apihelp-move-description": "Bir sayfayı taşı.",
+ "apihelp-move-param-from": "Taşımak istediğiniz sayfanın başlığı. $1fromid ile birlikte kullanılamaz.",
+ "apihelp-move-param-noredirect": "Yönlendirme oluşturmayın.",
+ "apihelp-opensearch-param-limit": "Verilecek azami sonuç sayısı.",
+ "apihelp-options-example-reset": "Tüm tercihleri sıfırla",
+ "api-help-title": "MediaWiki API yardımı",
+ "api-help-parameters": "{{PLURAL:$1|Parametre|Parametre}}:",
+ "api-help-param-limit": "$1 taneden fazla olamaz.",
+ "api-help-param-limit2": "$1 taneden fazla (botlar için $2) olamaz.",
+ "api-help-param-default": "Varsayılan: $1"
+}
diff --git a/includes/api/i18n/uk.json b/includes/api/i18n/uk.json
new file mode 100644
index 00000000..13ce4907
--- /dev/null
+++ b/includes/api/i18n/uk.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ата",
+ "A1"
+ ]
+ },
+ "apihelp-main-param-action": "Яку дію виконати.",
+ "apihelp-main-param-format": "Формат виводу.",
+ "apihelp-block-description": "Заблокувати користувача.",
+ "apihelp-block-param-user": "Ім'я користувача, IP-адреса або діапазон IP-адрес для блокування.",
+ "apihelp-block-param-reason": "Причина блокування.",
+ "apihelp-block-param-nocreate": "Заборонити створення облікових записів.",
+ "apihelp-createaccount-param-name": "Ім'я користувача.",
+ "apihelp-createaccount-param-password": "Пароль (ігнорується, якщо встановлено <var>$1mailpassword</var>).",
+ "apihelp-createaccount-param-domain": "Домен для зовнішньої аутентифікації (опціонально).",
+ "apihelp-edit-example-edit": "Редагувати сторінку",
+ "apihelp-edit-example-prepend": "Додати зміст на початок сторінки",
+ "apihelp-edit-example-undo": "Скасувати версії з 13579 по 13585 з автоматичним описом змін",
+ "apihelp-emailuser-description": "Надіслати електронного листа користувачеві",
+ "apihelp-emailuser-param-target": "Користувач, якому відправляється електронний лист.",
+ "apihelp-emailuser-param-subject": "Заголовок теми.",
+ "apihelp-emailuser-param-text": "Тіло листа.",
+ "apihelp-emailuser-param-ccme": "Надіслати копію цього повідомлення мені.",
+ "apihelp-emailuser-example-email": "Відправити листа користувачу \"WikiSysop\" з текстом \"Вміст\"",
+ "apihelp-expandtemplates-description": "Розгортає усі шаблони у вікітекст.",
+ "apihelp-expandtemplates-param-title": "Заголовок сторінки.",
+ "apihelp-expandtemplates-param-text": "Вікітекст для перетворення.",
+ "apihelp-move-param-ignorewarnings": "Ігнорувати всі попередження"
+}
diff --git a/includes/api/i18n/vi.json b/includes/api/i18n/vi.json
new file mode 100644
index 00000000..a027d9d8
--- /dev/null
+++ b/includes/api/i18n/vi.json
@@ -0,0 +1,156 @@
+{
+ "@metadata": {
+ "authors": [
+ "Minh Nguyen",
+ "Max20091",
+ "Dinhxuanduyet"
+ ]
+ },
+ "apihelp-main-param-action": "Tác vụ để thực hiện.",
+ "apihelp-main-param-format": "Định dạng của dữ liệu được cho ra.",
+ "apihelp-block-description": "Cấm người dùng.",
+ "apihelp-block-param-user": "Tên truy nhập, địa chỉ IP hoặc dãi IP mà bạn muốn chặn.",
+ "apihelp-block-param-reason": "Lý do cấm.",
+ "apihelp-block-param-nocreate": "Cấm tạo tài khoản.",
+ "apihelp-clearhasmsg-description": "Xóa cờ <code>hasmsg</code> cho người dùng hiện tại.",
+ "apihelp-clearhasmsg-example-1": "Xóa cờ <code>hasmsg</code> cho người dùng hiện tại",
+ "apihelp-compare-param-fromtitle": "So sánh tiêu đề đầu tiên.",
+ "apihelp-compare-param-fromid": "So sánh ID trang đầu tiên.",
+ "apihelp-compare-param-fromrev": "So sánh sửa đổi đầu tiên.",
+ "apihelp-compare-param-totitle": "So sánh tiêu đề thứ hai.",
+ "apihelp-compare-param-toid": "So sánh ID tran thứ hai.",
+ "apihelp-compare-param-torev": "So sánh sửa đổi thứ hai.",
+ "apihelp-compare-example-1": "Tạo một so sánh giữa phiên bản 1 và 2.",
+ "apihelp-createaccount-description": "Mở tài khoản mới.",
+ "apihelp-createaccount-param-name": "Tên người dùng.",
+ "apihelp-createaccount-param-password": "Mật khẩu (được bỏ qua nếu <var>$1mailpassword</var> được đặt).",
+ "apihelp-createaccount-param-domain": "Tên miền để xác thực bên ngoài (tùy chọn).",
+ "apihelp-createaccount-param-token": "Dấu hiệu mở tài khoản được lấy trong yêu cầu đầu tiên.",
+ "apihelp-createaccount-param-email": "Địa chỉ thư điện tử của thành viên (tùy chọn).",
+ "apihelp-createaccount-param-realname": "Tên thật của thành viên (tùy chọn).",
+ "apihelp-createaccount-param-mailpassword": "Nếu đặt bất kỳ giá trị nào, một mật khẩu ngẫu nhiên sẽ được email lại cho người dùng.",
+ "apihelp-createaccount-param-reason": "Lý do tùy chọn cho việc tạo tài khoản để đăng nhập.",
+ "apihelp-createaccount-param-language": "Mã ngôn ngữ để thiết lập mặc định cho người dùng (tùy chọn, mặc định là ngôn ngữ nội dung).",
+ "apihelp-createaccount-example-pass": "Tạo người dùng <kbd>người kiểm tra</kbd> với mật khẩu <kbd>test123</kbd>.",
+ "apihelp-createaccount-example-mail": "Tạo người dùng <kbd>người dùng kiểm tra email> và email một mật khẩu được tạo ra ngẫu nhiên.",
+ "apihelp-delete-description": "Xóa trang.",
+ "apihelp-delete-param-title": "Xóa tiêu đề của trang. Không thể sử dụng cùng với <var>$1pageid</var>.",
+ "apihelp-delete-param-pageid": "Xóa ID của trang. Không thể sử dụng cùng với <var>$1title</var>.",
+ "apihelp-delete-param-watch": "Thêm trang vào danh sách theo dõi của bạn.",
+ "apihelp-delete-param-unwatch": "Bỏ trang này khỏi danh sách theo dõi của bạn.",
+ "apihelp-delete-example-simple": "Xóa Trang Chính",
+ "apihelp-delete-example-reason": "Xóa Trang Chính với lý do “Chuẩn bị di chuyển”",
+ "apihelp-disabled-description": "Mô đun này đã bị vô hiệu hóa.",
+ "apihelp-edit-description": "Tạo và sửa trang.",
+ "apihelp-edit-param-section": "Số phần trang. 0 là phần đầu; “new” là phần mới.",
+ "apihelp-edit-param-sectiontitle": "Tên của phần mới.",
+ "apihelp-edit-param-text": "Nội dung trang.",
+ "apihelp-edit-param-summary": "Tóm lược sửa đổi. Cũng là tên phần khi $1section=new và $1sectiontitle không được đặt.",
+ "apihelp-edit-param-minor": "Sửa đổi nhỏ.",
+ "apihelp-edit-param-notminor": "Sửa đổi không nhỏ.",
+ "apihelp-edit-param-bot": "Đánh dấu sửa đổi này là do bot thực hiện.",
+ "apihelp-edit-param-createonly": "Không sửa đổi trang nếu nó đã tồn tại.",
+ "apihelp-edit-param-nocreate": "Gây lỗi nếu trang không tồn tại.",
+ "apihelp-edit-param-watch": "Thêm trang vào danh sách theo dõi của bạn.",
+ "apihelp-edit-param-unwatch": "Bỏ trang này khỏi danh sách theo dõi của bạn.",
+ "apihelp-edit-param-undo": "Hoàn tác sửa đổi này. Ghi đè $1text, $1prependtext và $ 1appendtext.",
+ "apihelp-edit-param-undoafter": "Hoàn tác tất cả các sửa đổi từ $1undo cho tới sửa đổi này. Nếu không được thiết lập, chỉ cần lùi lại một sửa đổi.",
+ "apihelp-edit-example-edit": "Sửa đổi trang",
+ "apihelp-edit-example-prepend": "Đưa <kbd>_&#95;NOTOC_&#95;</kbd> vào đầu trang",
+ "apihelp-edit-example-undo": "Lùi sửa các thay đổi 13579–13585 và tự động tóm lược",
+ "apihelp-emailuser-description": "Gửi thư cho người dùng.",
+ "apihelp-emailuser-param-target": "Người dùng để gửi thư điện tử cho.",
+ "apihelp-emailuser-param-subject": "Tiêu đề bức thư.",
+ "apihelp-emailuser-param-text": "Nội dung bức thư.",
+ "apihelp-emailuser-param-ccme": "Gửi bản sao của thư này cho tôi.",
+ "apihelp-emailuser-example-email": "Gửi thư điện tử cho thành viên “BQVWiki” với văn bản “Nội dung”",
+ "apihelp-expandtemplates-description": "Bung tất cả bản mẫu trong văn bản wiki.",
+ "apihelp-expandtemplates-param-title": "Tên trang.",
+ "apihelp-expandtemplates-param-text": "Văn bản wiki để bung.",
+ "apihelp-filerevert-param-comment": "Tải lên bình luận.",
+ "apihelp-filerevert-param-archivename": "Tên lưu trữ của bản sửa đổi để trở lại .",
+ "apihelp-filerevert-example-revert": "Hoàn nguyên <kbd>Wiki.png</kbd> veef phiên bản 2011-03-05T15 : 27:40Z",
+ "apihelp-help-description": "Hiển thị trợ giúp cho các mô-đun xác định.",
+ "apihelp-help-param-helpformat": "Định dạng của văn bản trợ giúp được cho ra.",
+ "apihelp-help-example-recursive": "Tất cả trợ giúp trong một trang",
+ "apihelp-help-example-help": "Trợ giúp cho chính bản thân module trợ giúp",
+ "apihelp-help-example-query": "Trợ giúp cho hai module con truy vấn",
+ "apihelp-imagerotate-description": "Xoay một hoặc nhiều hình ảnh.",
+ "apihelp-imagerotate-param-rotation": "Độ xoay hình ảnh theo chiều kim đồng hồ.",
+ "apihelp-imagerotate-example-simple": "Xoay [[:Tập tin:Ví dụ.jpg]] 90 độ",
+ "apihelp-imagerotate-example-generator": "Xoay tất cả các hình ảnh trong [[:Thể loại:Búng]] 180 độ",
+ "apihelp-login-param-name": "Tên người dùng.",
+ "apihelp-login-param-password": "Mật khẩu.",
+ "apihelp-login-param-domain": "Tên miền (tùy chọn).",
+ "apihelp-login-param-token": "Dấu hiệu đăng nhập được lấy trong yêu cầu đầu tiên.",
+ "apihelp-login-example-gettoken": "Lấy dấu hiệu đăng nhập",
+ "apihelp-login-example-login": "Đăng nhập",
+ "apihelp-logout-example-logout": "Đăng xuất người dùng hiện tại",
+ "apihelp-move-description": "Di chuyển trang.",
+ "apihelp-move-param-reason": "Lý do di chuyển.",
+ "apihelp-move-param-noredirect": "Không tạo trang đổi hướng.",
+ "apihelp-move-param-ignorewarnings": "Bỏ qua tất cả các cảnh báo.",
+ "apihelp-opensearch-description": "Tìm kiếm trong wiki qua giao thức OpenSearch.",
+ "apihelp-opensearch-param-search": "Chuỗi tìm kiếm.",
+ "apihelp-opensearch-param-limit": "Đa số kết quả để cho ra.",
+ "apihelp-opensearch-param-namespace": "Không gian tên để tìm kiếm.",
+ "apihelp-opensearch-param-format": "Định dạng kết quả được cho ra.",
+ "apihelp-opensearch-example-te": "Tìm trang bắt đầu với “Te”",
+ "apihelp-options-example-reset": "Mặc định lại các tùy chọn",
+ "apihelp-paraminfo-param-helpformat": "Định dạng chuỗi trợ giúp.",
+ "apihelp-parse-param-summary": "Lời tóm lược để phân tích.",
+ "apihelp-parse-param-section": "Chỉ truy xuất nội dung của số bộ phận này.",
+ "apihelp-parse-param-disablepp": "Vô hiệu hóa phân tích cú pháp đầu ra của Báo cáo PP .",
+ "apihelp-parse-example-page": "Phân tích trang.",
+ "apihelp-parse-example-text": "Phân tích văn bản wiki.",
+ "apihelp-parse-example-texttitle": "Phân tích văn bản wiki theo tên trang.",
+ "apihelp-parse-example-summary": "Phân tích lời tóm lược.",
+ "apihelp-protect-example-protect": "Khóa trang.",
+ "apihelp-protect-example-unprotect": "Mở khóa trang bằng cách đặt hạn chế thành “all”",
+ "apihelp-protect-example-unprotect2": "Mở khóa trang bằng cách không đặt hạn chế nào",
+ "apihelp-purge-param-forcelinkupdate": "Cập nhật các bảng liên kết.",
+ "apihelp-purge-example-generator": "Làm mới 10 trang đầu tiên trong không gian tên chính",
+ "apihelp-query-param-prop": "Các thuộc tính để lấy khi truy vấn các trang.",
+ "apihelp-query-param-list": "Các danh sách để lấy.",
+ "apihelp-query-param-meta": "Siêu dữ liệu để lấy.",
+ "apihelp-query+allcategories-param-dir": "Hướng xếp loại.",
+ "apihelp-rollback-description": "Hoàn tác chỉnh sửa cuối cùng của trang này.\n\nNếu người dùng cuối cùng đã cỉnh sửa trang này nhiều lần, tất cả chúng sẽ được hoàn tác lại như ban đầu.",
+ "apihelp-format-example-generic": "Định dạng kết quả truy vấn dưới dạng $1",
+ "apihelp-dbg-description": "Cho ra dữ liệu dưới dạng var_export() của PHP.",
+ "apihelp-dbgfm-description": "Cho ra dữ liệu dưới dạng var_export() của PHP (định dạng bằng HTML).",
+ "apihelp-dump-description": "Cho ra dữ liệu dưới dạng var_dump() của PHP.",
+ "apihelp-dumpfm-description": "Cho ra dữ liệu dưới dạng var_dump() của PHP (định dạng bằng HTML).",
+ "apihelp-json-description": "Cho ra dữ liệu dưới dạng JSON.",
+ "apihelp-jsonfm-description": "Cho ra dữ liệu dưới dạng JSON (định dạng bằng HTML).",
+ "apihelp-none-description": "Không cho ra gì.",
+ "apihelp-rawfm-description": "Cho ra dữ liệu với các phần tử gỡ lỗi dưới dạng JSON (định dạng bằng HTML).",
+ "apihelp-txt-description": "Cho ra dữ liệu dưới dạng print_r() của PHP.",
+ "apihelp-txtfm-description": "Cho ra dữ liệu dưới dạng print_r() của PHP (định dạng bằng HTML).",
+ "apihelp-wddx-description": "Cho ra dữ liệu dưới dạng WDDX.",
+ "apihelp-wddxfm-description": "Cho ra dữ liệu dưới dạng WDDX (định dạng bằng HTML).",
+ "apihelp-xml-description": "Cho ra dữ liệu dưới dạng XML.",
+ "apihelp-xmlfm-description": "Cho ra dữ liệu dưới dạng XML (định dạng bằng HTML).",
+ "apihelp-yaml-description": "Cho ra dữ liệu dưới dạng YAML.",
+ "apihelp-yamlfm-description": "Cho ra dữ liệu dưới dạng YAML (định dạng bằng HTML).",
+ "api-format-title": "Kết quả API MediaWiki",
+ "api-help-title": "Trợ giúp về API MediaWiki",
+ "api-help-main-header": "Mô đun chính",
+ "api-help-flag-deprecated": "Mô đun này đã bị phản đối.",
+ "api-help-flag-readrights": "Mô đun này cần quyền đọc.",
+ "api-help-flag-writerights": "Mô đun này cần quyền ghi.",
+ "api-help-flag-mustbeposted": "Mô đun này chỉ có nhận các yêu cầu POST.",
+ "api-help-parameters": "{{PLURAL:$1|Tham số|Các tham số}}:",
+ "api-help-param-deprecated": "Bị phản đối.",
+ "api-help-param-required": "Tham số này là bắt buộc.",
+ "api-help-param-list": "{{PLURAL:$1|1=Một giá trị|2=Các giá trị (phân tách bằng “{{!}}”)}}: $2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Cần phải để trống|Cần phải để trống hoặc là $2}}",
+ "api-help-param-limit": "Không cho phép hơn $1.",
+ "api-help-param-limit2": "Không cho phép hơn $1 ($2 đối với các bot).",
+ "api-help-param-multi-separate": "Phân tách các giá trị bằng “|”.",
+ "api-help-param-default": "Mặc định: $1",
+ "api-help-param-default-empty": "Mặc định: <span class=\"apihelp-empty\">(trống)</span>",
+ "api-help-examples": "{{PLURAL:$1|Ví dụ|Các ví dụ}}:",
+ "api-help-permissions": "{{PLURAL:$1|Quyền hạn|Các quyền hạn}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1}}Cấp cho: $2",
+ "api-credits-header": "Ghi công"
+}
diff --git a/includes/api/i18n/zh-hans.json b/includes/api/i18n/zh-hans.json
new file mode 100644
index 00000000..722803bf
--- /dev/null
+++ b/includes/api/i18n/zh-hans.json
@@ -0,0 +1,782 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gaoxuewei",
+ "Linforest",
+ "Liuxinyu970226",
+ "Papapasan",
+ "LNDDYL",
+ "Shizhao",
+ "Yfdyh000",
+ "JuneAugsut",
+ "EagerLin",
+ "Simon xianyu",
+ "Kuailong"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|文档]]\n* [[mw:API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>本页所展示的所有特性都应正常工作,但是API仍在开发当中,将会随时变化。请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:API:Errors_and_warnings|API: 错误与警告]]。",
+ "apihelp-main-param-action": "要执行的操作。",
+ "apihelp-main-param-format": "输出的格式。",
+ "apihelp-main-param-maxlag": "最大延迟可被用于MediaWiki安装于数据库复制集中。要保存导致更多网站复制延迟的操作,此参数可使客户端等待直到复制延迟少于指定值时。万一发生过多延迟,错误代码<samp>maxlag</samp>会返回消息,例如<samp>等待$host中:延迟$lag秒</samp>。<br />参见[[mw:Manual:Maxlag_parameter|Manual: Maxlag parameter]]以获取更多信息。",
+ "apihelp-main-param-smaxage": "设置<code>s-maxage</code>页顶至这些秒。错误不会缓存。",
+ "apihelp-main-param-maxage": "设置<code>max-age</code>页顶至这些秒。错误不会缓存。",
+ "apihelp-main-param-assert": "如果设置为<kbd>user</kbd>就验证用户是否登录,或如果设置为<kbd>bot</kbd>就验证是否有机器人用户权限。",
+ "apihelp-main-param-requestid": "任何在此提供的值将包含在响应中。可能可以用以区别请求。",
+ "apihelp-main-param-servedby": "包含保存结果请求的主机名。",
+ "apihelp-main-param-curtimestamp": "在结果中包括当前时间戳。",
+ "apihelp-main-param-origin": "当通过跨域名AJAX请求(CORS)访问API时,设置此作为起始域名。这必须包括在任何pre-flight请求中,并因此必须是请求的URI的一部分(而不是POST正文)。这必须匹配<code>Origin</code>中的一个起点:从头到底,因此它已经设置为像<kbd>https://zh.wikipedia.org</kbd>或<kbd>https://meta.wikimedia.org</kbd>的东西。如果此参数不匹配<code>Origin</code>页顶,就返回403错误响应。如果此参数匹配<code>Origin</code>页顶并且起点被白名单,将设置一个<code>Access-Control-Allow-Origin</code>开头。",
+ "apihelp-main-param-uselang": "用于消息翻译的语言。代码列表可从<kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>通过<kbd>siprop=languages</kbd>获取,或指定<kbd>user</kbd>以使用当前用户的语言设置,或指定<kbd>content</kbd>以使用此wiki的内容语言。",
+ "apihelp-block-description": "封禁一位用户。",
+ "apihelp-block-param-user": "您要封禁的用户、IP地址或IP地址段。",
+ "apihelp-block-param-expiry": "到期时间。可以是相对时间(例如<kbd>5 months</kbd>或<kbd>2 weeks</kbd>)或绝对时间(例如<kbd>2014-09-18T12:34:56Z</kbd>)。如果设置为<kbd>infinite</kbd>、<kbd>indefinite</kbd>或<kbd>never</kbd>,封禁将无限期。",
+ "apihelp-block-param-reason": "封禁的原因",
+ "apihelp-block-param-anononly": "只封禁匿名用户(也就是说禁止此 IP 地址的匿名编辑)。",
+ "apihelp-block-param-nocreate": "防止创建帐户。",
+ "apihelp-block-param-autoblock": "自动封禁最近使用的IP地址,以及以后他们尝试登陆使用的IP地址。",
+ "apihelp-block-param-noemail": "阻止用户通过wiki发送电子邮件。(需要<code>blockemail</code>权限)。",
+ "apihelp-block-param-hidename": "从封禁日志中隐藏用户名。(需要<code>hideuser</code>权限)。",
+ "apihelp-block-param-allowusertalk": "允许用户编辑自己的讨论页(取决于<var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var>)。",
+ "apihelp-block-param-reblock": "如果该用户已被封禁,则覆盖已有的封禁。",
+ "apihelp-block-param-watchuser": "监视用户或该 IP 的用户页和讨论页。",
+ "apihelp-block-example-ip-simple": "封禁IP地址<kbd>192.0.2.5</kbd>三天,原因<kbd>First strike</kbd>。",
+ "apihelp-block-example-user-complex": "无限期封禁用户<kbd>Vandal</kbd>,原因<kbd>Vandalism</kbd>,并阻止新账户创建和电子邮件发送。",
+ "apihelp-checktoken-description": "从<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>检查令牌有效性。",
+ "apihelp-checktoken-param-type": "已开始测试的令牌类型。",
+ "apihelp-checktoken-param-token": "要测试的令牌。",
+ "apihelp-checktoken-param-maxtokenage": "令牌的最大允许年龄,以秒计。",
+ "apihelp-checktoken-example-simple": "测试<kbd>csrf</kbd>令牌的有效性。",
+ "apihelp-clearhasmsg-description": "清除当前用户的<code>hasmsg</code>标记。",
+ "apihelp-clearhasmsg-example-1": "清除当前用户的<code>hasmsg</code>标记。",
+ "apihelp-compare-description": "获得2个页面之间的差别。\n\n用于“from”和“to”的修订版本号、页面标题或页面 ID 必须获得通过。",
+ "apihelp-compare-param-fromtitle": "要比较的第一个标题。",
+ "apihelp-compare-param-fromid": "要比较的第一个页面 ID。",
+ "apihelp-compare-param-fromrev": "要比较的第一个修订版本。",
+ "apihelp-compare-param-totitle": "要比较的第二个标题。",
+ "apihelp-compare-param-toid": "要比较的第二个页面 ID。",
+ "apihelp-compare-param-torev": "要比较的第二个修订版本。",
+ "apihelp-compare-example-1": "在版本1和2中创建差异",
+ "apihelp-createaccount-description": "创建一个新用户账户。",
+ "apihelp-createaccount-param-name": "用户名",
+ "apihelp-createaccount-param-password": "密码(如果设置<var>$1mailpassword</var>则忽略)。",
+ "apihelp-createaccount-param-domain": "外部身份验证域 (可选)。",
+ "apihelp-createaccount-param-token": "在第一个请求中获得的帐户创建标记。",
+ "apihelp-createaccount-param-email": "用户的电子邮件地址(可选)。",
+ "apihelp-createaccount-param-realname": "用户的真实姓名(可选)。",
+ "apihelp-createaccount-param-mailpassword": "如果设置为任何值,将向用户发送一个随机密码。",
+ "apihelp-createaccount-param-reason": "将要放在日志中的,关于创建帐户的可选原因。",
+ "apihelp-createaccount-param-language": "要为用户设置为默认值的语言代码(可选,默认为内容语言)。",
+ "apihelp-createaccount-example-pass": "创建用户<kbd>testuser</kbd>和密码<kbd>test123</kbd>。",
+ "apihelp-createaccount-example-mail": "创建用户<kbd>testmailuser</kbd>并电邮发送一个随机生成的密码。",
+ "apihelp-delete-description": "删除一个页面。",
+ "apihelp-delete-param-title": "你所希望删除的页面的标题。不能与<var>$1pageid</var>一起使用。",
+ "apihelp-delete-param-pageid": "要删除的页面的页面 ID。不能与<var>$1title</var>一起使用。",
+ "apihelp-delete-param-reason": "删除原因。如果未设置,将使用一个自动生成的原因。",
+ "apihelp-delete-param-watch": "将该页面加入当前用户的监视列表。",
+ "apihelp-delete-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
+ "apihelp-delete-param-unwatch": "将该页面从当前用户的监视列表删除。",
+ "apihelp-delete-param-oldimage": "由[[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]]提供的要删除的旧图片名称。",
+ "apihelp-delete-example-simple": "删除<kbd>Main Page</kbd>。",
+ "apihelp-delete-example-reason": "删除<kbd>Main Page</kbd>,原因<kbd>Preparing for move</kbd>。",
+ "apihelp-disabled-description": "此模块已禁用。",
+ "apihelp-edit-description": "创建和编辑页面。",
+ "apihelp-edit-param-title": "您希望编辑的页面标题。不能与<var>$1pageid</var>一起使用。",
+ "apihelp-edit-param-pageid": "要编辑的页面的页面 ID。不能与<var>$1title</var>一起使用。",
+ "apihelp-edit-param-section": "段落数。<kbd>0</kbd>用于首段,<kbd>new</kbd>用于新的段落。",
+ "apihelp-edit-param-sectiontitle": "新小节的标题。",
+ "apihelp-edit-param-text": "页面内容。",
+ "apihelp-edit-param-summary": "编辑摘要。当$1section=new且未设置$1sectiontitle时,还包括小节标题。",
+ "apihelp-edit-param-minor": "小编辑。",
+ "apihelp-edit-param-notminor": "不是小编辑。",
+ "apihelp-edit-param-bot": "标记此编辑为机器人编辑。",
+ "apihelp-edit-param-basetimestamp": "基础修订的时间戳,用于检测编辑冲突。也许可以通过[[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]]得到。",
+ "apihelp-edit-param-starttimestamp": "编辑过程开始的时间戳,用于检测编辑冲突。当开始编辑过程时(例如当加载要编辑的页面时)使用<var>[[Special:ApiHelp/main|curtimestamp]]</var>可能取得一个适当的值。",
+ "apihelp-edit-param-recreate": "覆盖有关该页面在此期间已被删除的任何错误。",
+ "apihelp-edit-param-createonly": "不要编辑页面,如果已经存在。",
+ "apihelp-edit-param-nocreate": "如果该页面不存在,则抛出一个错误。",
+ "apihelp-edit-param-watch": "将页面加入当前用户的监视列表。",
+ "apihelp-edit-param-unwatch": "将页面从当前用户的监视列表中移除。",
+ "apihelp-edit-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
+ "apihelp-edit-param-md5": "$1text参数或$1prependtext和$1appendtext级联参数的MD5哈希值。如果设置,除非哈希值正确否则编辑无法完成。",
+ "apihelp-edit-param-prependtext": "将该文本添加到该页面的开始。覆盖$1text。",
+ "apihelp-edit-param-appendtext": "将该文本添加到该页面的结尾。覆盖$1text。\n\n采用$1section=new来添加一个新的章节,而不是这个参数。",
+ "apihelp-edit-param-undo": "撤销此次修订。覆盖$1text、$1prependtext和$1appendtext。",
+ "apihelp-edit-param-undoafter": "撤销从$1undo至此的所有修订。如果不设置就撤销一次修订。",
+ "apihelp-edit-param-redirect": "自动解析重定向。",
+ "apihelp-edit-param-contentformat": "用于输入文本的内容串行化格式。",
+ "apihelp-edit-param-contentmodel": "新内容的内容模型。",
+ "apihelp-edit-param-token": "令牌应总是发送为最后参数,或至少在$1text参数之后。",
+ "apihelp-edit-example-edit": "编辑一个页面",
+ "apihelp-edit-example-prepend": "页面中预置<kbd>_&#95;NOTOC_&#95;</kbd>",
+ "apihelp-edit-example-undo": "通过13585撤销修订版本13579并自动填写编辑摘要",
+ "apihelp-emailuser-description": "电子邮件联系一位用户。",
+ "apihelp-emailuser-param-target": "电子邮件的目标用户。",
+ "apihelp-emailuser-param-subject": "主题页眉。",
+ "apihelp-emailuser-param-text": "邮件正文。",
+ "apihelp-emailuser-param-ccme": "给我发送一份该邮件的副本。",
+ "apihelp-emailuser-example-email": "向用户<kbd>WikiSysop</kbd>发送邮件,带文字<kbd>Content</kbd>。",
+ "apihelp-expandtemplates-description": "展开维基文本中的所有模板。",
+ "apihelp-expandtemplates-param-title": "页面标题。",
+ "apihelp-expandtemplates-param-text": "要转换的wiki文本。",
+ "apihelp-expandtemplates-param-revid": "修订版本ID,用于<nowiki>{{REVISIONID}}</nowiki>和类似变体。",
+ "apihelp-expandtemplates-param-prop": "要获取的那条信息:\n;wikitext:展开的wiki文本。\n;categories:任何在不代表wiki文本输出的输入框出现的分类。\n;properties:由wiki文本中扩充的魔术字定义的页面属性。\n;volatile:输出是否不稳定,并且不应在任何页面中再度使用。\n;ttl:结果的哪个缓存后等待最长时间应无效化。\n;parsetree:输入的XML解析树。\n注意如果没有选定值,结果将包含wiki文本,但将以弃用的格式显示。",
+ "apihelp-expandtemplates-param-includecomments": "输出时是否包含HTML摘要。",
+ "apihelp-expandtemplates-param-generatexml": "生成XML解析树(取代自$1prop=parsetree)。",
+ "apihelp-expandtemplates-example-simple": "展开wiki文本<kbd><nowiki>{{Project:Sandbox}}</nowiki></kbd>。",
+ "apihelp-feedcontributions-description": "返回用户贡献纲要。",
+ "apihelp-feedcontributions-param-feedformat": "纲要的格式。",
+ "apihelp-feedcontributions-param-user": "获取哪些用户的贡献。",
+ "apihelp-feedcontributions-param-namespace": "过滤哪些命名空间的贡献。",
+ "apihelp-feedcontributions-param-year": "起始年份(及更早)。",
+ "apihelp-feedcontributions-param-month": "起始月份(及更早)。",
+ "apihelp-feedcontributions-param-tagfilter": "过滤有这些标签的贡献者。",
+ "apihelp-feedcontributions-param-deletedonly": "仅显示已删除的贡献。",
+ "apihelp-feedcontributions-param-toponly": "仅仅显示那些作为最新修订的编辑。",
+ "apihelp-feedcontributions-param-newonly": "仅仅显示那些作为页面创建的编辑。",
+ "apihelp-feedcontributions-param-showsizediff": "显示修订版本之间的大小差别。",
+ "apihelp-feedcontributions-example-simple": "返回用户<kbd>Example</kbd>的贡献。",
+ "apihelp-feedrecentchanges-description": "返回最新变更纲要。",
+ "apihelp-feedrecentchanges-param-feedformat": "纲要的格式。",
+ "apihelp-feedrecentchanges-param-namespace": "用于限制结果的命名空间。",
+ "apihelp-feedrecentchanges-param-invert": "除所选定者外的所有命名空间。",
+ "apihelp-feedrecentchanges-param-associated": "包括相关的命名空间(讨论页或主要)。",
+ "apihelp-feedrecentchanges-param-days": "用于限制结果的天数。",
+ "apihelp-feedrecentchanges-param-limit": "所要返回结果的最大数目。",
+ "apihelp-feedrecentchanges-param-from": "显示自那时以来的更改。",
+ "apihelp-feedrecentchanges-param-hideminor": "隐藏小更改。",
+ "apihelp-feedrecentchanges-param-hidebots": "隐藏机器人所做的更改。",
+ "apihelp-feedrecentchanges-param-hideanons": "隐藏匿名用户做出的更改。",
+ "apihelp-feedrecentchanges-param-hideliu": "隐藏注册用户做出的更改。",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "隐藏已巡查更改。",
+ "apihelp-feedrecentchanges-param-hidemyself": "隐藏当前用户做出的更改。",
+ "apihelp-feedrecentchanges-param-tagfilter": "按标签过滤。",
+ "apihelp-feedrecentchanges-param-target": "仅仅显示从该页面链出的那些页面的变更。",
+ "apihelp-feedrecentchanges-param-showlinkedto": "仅仅显示链入到该页面的那些页面的变更。",
+ "apihelp-feedrecentchanges-example-simple": "显示最近更改",
+ "apihelp-feedrecentchanges-example-30days": "显示最近30天的更改",
+ "apihelp-feedwatchlist-description": "返回监视列表纲要。",
+ "apihelp-feedwatchlist-param-feedformat": "纲要的格式。",
+ "apihelp-feedwatchlist-param-hours": "列出从现在起数小时内修改的页面。",
+ "apihelp-feedwatchlist-param-linktosections": "如果可能的话,直接链接到已变更的小节。",
+ "apihelp-feedwatchlist-example-default": "显示监视列表订阅",
+ "apihelp-feedwatchlist-example-all6hrs": "显示过去6小时内受监视页面的所有更改。",
+ "apihelp-filerevert-description": "回退一个文件至某一旧版本。",
+ "apihelp-filerevert-param-filename": "目标文件名,不包含前缀“File:”。",
+ "apihelp-filerevert-param-comment": "上传评论。",
+ "apihelp-filerevert-param-archivename": "恢复到修订版存档名称。",
+ "apihelp-filerevert-example-revert": "回退<kbd>Wiki.png</kbd>至<kbd>2011-03-05T15:27:40Z</kbd>的版本。",
+ "apihelp-help-description": "显示指定模块的帮助。",
+ "apihelp-help-param-modules": "用于显示帮助的模块(<var>action</var>和<var>format</var>参数值,或<kbd>main</kbd>)。可通过<kbd>+</kbd>指定子模块。",
+ "apihelp-help-param-submodules": "包括给定名称模块的子模块的帮助。",
+ "apihelp-help-param-recursivesubmodules": "包括递归子模块的帮助。",
+ "apihelp-help-param-helpformat": "帮助的输出格式。",
+ "apihelp-help-param-wrap": "在一个标准API响应结构中包裹输出。",
+ "apihelp-help-param-toc": "在HTML输出中包括目录。",
+ "apihelp-help-example-main": "主模块帮助",
+ "apihelp-help-example-recursive": "一个页面中的所有帮助",
+ "apihelp-help-example-help": "帮助模块本身的帮助",
+ "apihelp-help-example-query": "两个查询子模块的帮助",
+ "apihelp-imagerotate-description": "旋转一幅或多幅图像。",
+ "apihelp-imagerotate-param-rotation": "顺时针旋转图像的度数。",
+ "apihelp-imagerotate-example-simple": "<kbd>90</kbd>度旋转<kbd>File:Example.png</kbd>。",
+ "apihelp-imagerotate-example-generator": "将<kbd>Category:Flip</kbd>之中的所有图像旋转<kbd>180</kbd>度。",
+ "apihelp-import-description": "从另一个wiki导入一个页面,或一个XML文件。\n\n注意当发送一个用于<var>xml</var>参数的文件时,HTTP POST必须作为一次文件上传完成(也就是使用multipart/form-data)。",
+ "apihelp-import-param-summary": "导入摘要。",
+ "apihelp-import-param-xml": "上传的XML文件。",
+ "apihelp-import-param-interwikisource": "用于跨wiki导入:导入的来源wiki。",
+ "apihelp-import-param-interwikipage": "用于跨wiki导入:导入的页面。",
+ "apihelp-import-param-fullhistory": "用于跨wiki导入:完整导入历史,而不只是最新版本。",
+ "apihelp-import-param-templates": "用于跨wiki导入:连带导入所有包含的模板。",
+ "apihelp-import-param-namespace": "用于跨wiki导入:导入到此名字空间。",
+ "apihelp-import-param-rootpage": "导入作为此页面的子页面。",
+ "apihelp-import-example-import": "将页面[[meta:Help:Parserfunctions]]连带完整历史导入至100名字空间。",
+ "apihelp-login-description": "登录并获得身份验证Cookie。\n\n在成功登录的情况下,所需的Cookie将包含在HTTP响应头中。在登录失败的情况下,进一步的尝试可能会被自动密码猜解攻击的限制所遏制。",
+ "apihelp-login-param-name": "用户名。",
+ "apihelp-login-param-password": "密码。",
+ "apihelp-login-param-domain": "域名(可选)。",
+ "apihelp-login-param-token": "在首个请求中获得的登录令牌。",
+ "apihelp-login-example-gettoken": "检索登录令牌",
+ "apihelp-login-example-login": "登录",
+ "apihelp-logout-description": "退出并清除会话数据。",
+ "apihelp-logout-example-logout": "退出当前用户",
+ "apihelp-managetags-description": "执行有关更改标签的管理任务。",
+ "apihelp-managetags-param-operation": "要执行哪个操作:\n;create:创建一个新的更改标签供手动使用。\n;delete:从数据库中移除一个更改标签,包括移除已使用在所有修订版本、最近更改记录和日志记录上的该标签。\n;activate:激活一个更改标签,允许用户手动应用它。\n;deactivate:停用一个更改标签,阻止用户手动应用它。",
+ "apihelp-managetags-param-tag": "要创建、删除、激活或取消激活的标签。要创建标签,标签必须不存在。要删除标签,标签必须存在。要激活标签,标签必须存在,且不被任何扩展使用。要取消激活标签,标签必须当前处于激活状态,且被手动定义。",
+ "apihelp-managetags-param-reason": "一个创建、删除、激活或停用标签时的原因,可选。",
+ "apihelp-managetags-param-ignorewarnings": "是否忽略操作期间发生的任何警告。",
+ "apihelp-managetags-example-create": "创建一个名为<kbd>spam</kbd>的标签,原因<kbd>For use in edit patrolling</kbd>",
+ "apihelp-managetags-example-delete": "删除<kbd>vandlaism</kbd>标签,原因<kbd>Misspelt</kbd>",
+ "apihelp-managetags-example-activate": "激活一个名为<kbd>spam</kbd>的标签,原因<kbd>For use in edit patrolling</kbd>",
+ "apihelp-managetags-example-deactivate": "停用一个名为<kbd>spam</kbd>的标签,原因<kbd>No longer required</kbd>",
+ "apihelp-move-description": "移动一个页面。",
+ "apihelp-move-param-from": "要重命名的页面标题。不能与<var>$1fromid</var>一起使用。",
+ "apihelp-move-param-fromid": "您希望移动的页面ID。不能与<var>$1from</var>一起使用。",
+ "apihelp-move-param-to": "页面重命名的目标标题。",
+ "apihelp-move-param-reason": "重命名的原因。",
+ "apihelp-move-param-movetalk": "重命名讨论页,如果存在。",
+ "apihelp-move-param-movesubpages": "重命名子页面,如果可以。",
+ "apihelp-move-param-noredirect": "不要创建重定向。",
+ "apihelp-move-param-watch": "将页面和重定向加入至当前用户的监视列表中。",
+ "apihelp-move-param-unwatch": "从当前用户的监视列表中移除页面及重定向。",
+ "apihelp-move-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
+ "apihelp-move-param-ignorewarnings": "忽略任何警告。",
+ "apihelp-move-example-move": "移动<kbd>坏标题</kbd>到<kbd>好标题</kbd>并且不留下重定向。",
+ "apihelp-opensearch-description": "使用OpenSearch协议搜索本wiki。",
+ "apihelp-opensearch-param-search": "搜索字符串。",
+ "apihelp-opensearch-param-limit": "要返回的结果最大数。",
+ "apihelp-opensearch-param-namespace": "搜索的名字空间。",
+ "apihelp-opensearch-param-suggest": "如果<var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var>设置为false则不做任何事情。",
+ "apihelp-opensearch-param-redirects": "如何处理重定向:\n;return:返回重定向本身。\n;resolve:返回目标页面。可能返回少于$1limit个结果。\n由于历史原因,$1format=json默认为\"return\",其他格式默认为\"resolve\"。",
+ "apihelp-opensearch-param-format": "输出格式。",
+ "apihelp-opensearch-example-te": "查找以<kbd>Te</kbd>开头的页面。",
+ "apihelp-options-description": "更改当前用户的偏好设置。\n\n只有注册在核心或者已安装扩展中的选项,或者具有\"userjs-\"键值前缀(旨在被用户脚本使用)的选项可被设置。",
+ "apihelp-options-param-reset": "重置偏好设置到网站默认设置。",
+ "apihelp-options-param-resetkinds": "当<var>$1reset</var>选项被设置时,要重置的选项类型列表。",
+ "apihelp-options-param-change": "更改列表,以name=value格式化(例如skin=vector)。值不能包含管道字符。如果没提供值(甚至没有等号),例如optionname|otheroption|...,选项将重置为默认值。",
+ "apihelp-options-param-optionname": "应设置为由<var>$1optionvalue</var>提供值的选项名称。",
+ "apihelp-options-param-optionvalue": "使用<var>$1选项名</var>指定的选项值中,可以包含管道字符",
+ "apihelp-options-example-reset": "重置所有用户设置",
+ "apihelp-options-example-change": "更改<kbd>skin</kbd>和<kbd>hideminor</kbd>设置。",
+ "apihelp-options-example-complex": "重置所有设置,然后设置<kbd>皮肤</kbd>和<kbd>昵称</kbd>。",
+ "apihelp-paraminfo-description": "获取关于 API 模块的信息。",
+ "apihelp-paraminfo-param-modules": "模块名称(<var>action</var>和<var>format</var>参数值,或<kbd>main</kbd>)的列表。可通过<kbd>+</kbd>指定子模块。",
+ "apihelp-paraminfo-param-helpformat": "帮助字符串的格式。",
+ "apihelp-paraminfo-param-querymodules": "查询模块名称(<var>prop</var>、<var>meta</var>或<var>list</var>参数值)的列表。使用<kbd>$1modules=query+foo</kbd>而不是<kbd>$1querymodules=foo</kbd>。",
+ "apihelp-paraminfo-param-mainmodule": "获得有关主要(最高级)模块的信息。也可使用<kbd>$1modules=main</kbd>。",
+ "apihelp-paraminfo-param-pagesetmodule": "获得有关页面设置模块(提供titles=和朋友)的信息。",
+ "apihelp-paraminfo-param-formatmodules": "格式模块名称(<var>format</var>参数的值)的列表。也可使用<var>$1modules</var>。",
+ "apihelp-paraminfo-example-1": "显示<kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>、<kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>、<kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>和<kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>的信息。",
+ "apihelp-parse-description": "解析内容并返回解析器输出。\n\n参见<kbd>[[Special:ApiHelp/query|action=query]]</kbd>的各种prop-module以从页面的当前版本获得信息。\n\n这里有几种方法可以指定解析的文本:\n# 指定一个页面或修订,使用<var>$1page</var>、<var>$1pageid</var>或<var>$1oldid</var>。\n# 明确指定内容,使用<var>$1text</var>、<var>$1title</var>和<var>$1contentmodel</var>。\n# 只指定一段摘要解析。<var>$1prop</var>应提供一个空值。",
+ "apihelp-parse-param-title": "文本属于的页面标题。如果省略,<var>$1contentmodel</var>就必须被指定,且[[API]]将作为标题使用。",
+ "apihelp-parse-param-text": "要解析的文本。使用<var>$1title</var>或<var>$1contentmodel</var>以控制内容模型。",
+ "apihelp-parse-param-summary": "所要解析的摘要。",
+ "apihelp-parse-param-page": "解析此页的内容。不能与<var>$1text</var>和<var>$1title</var>一起使用。",
+ "apihelp-parse-param-pageid": "解析此页的内容。覆盖<var>$1page</var>。",
+ "apihelp-parse-param-redirects": "如果<var>$1page</var>或<var>$1pageid</var>被设置为一个重定向,则解析它。",
+ "apihelp-parse-param-oldid": "解析该修订版本的内容。覆盖<var>$1page</var>和<var>$1pageid</var>。",
+ "apihelp-parse-param-pst": "在解析输入前,对输入做一次保存前变换处理。仅当使用文本时有效。",
+ "apihelp-parse-param-effectivelanglinks": "包含由扩展提供的语言链接(用于与<kbd>$1prop=langlinks</kbd>一起使用)。",
+ "apihelp-parse-param-section": "只检索此段数的内容,或只当<kbd>new</kbd>生成新的段落时检索。\n\n<kbd>new</kbd>段落只当指定<var>text</var>时受尊重。",
+ "apihelp-parse-param-sectiontitle": "当<var>section</var>为<kbd>new</kbd>时新段落标题。\n\n不像页面编辑,当省略或为空时将不会备选为<var>summary</var>。",
+ "apihelp-parse-param-disablepp": "从解析器输出中禁用PP报告。",
+ "apihelp-parse-param-disableeditsection": "从解析器输出中禁用编辑段落链接。",
+ "apihelp-parse-param-generatexml": "生成XML解析树(需要内容模型<code>$1</code>)。",
+ "apihelp-parse-param-preview": "在预览模式下解析。",
+ "apihelp-parse-param-sectionpreview": "在小节预览模式下解析 (同时要启用预览模式)。",
+ "apihelp-parse-param-disabletoc": "在输出中禁用目录。",
+ "apihelp-parse-example-page": "解析一个页面。",
+ "apihelp-parse-example-text": "解析wiki文本。",
+ "apihelp-parse-example-texttitle": "解析维基文本,指定页面标题。",
+ "apihelp-parse-example-summary": "解析一个摘要。",
+ "apihelp-patrol-description": "巡查页面或修订版本。",
+ "apihelp-patrol-param-rcid": "所要巡查的最近变更 ID。",
+ "apihelp-patrol-param-revid": "要巡查的修订版本ID。",
+ "apihelp-patrol-example-rcid": "巡查一次最近更改",
+ "apihelp-patrol-example-revid": "巡查一次修订",
+ "apihelp-protect-description": "更改页面的保护等级。",
+ "apihelp-protect-param-title": "要(解除)保护的页面标题。不能与$1pageid一起使用。",
+ "apihelp-protect-param-pageid": "要(解除)保护的页面ID。不能与$1title一起使用。",
+ "apihelp-protect-param-protections": "保护等级列表,格式:<kbd>action=level</kbd>(例如<kbd>edit=sysop</kbd>)。\n\n<strong>注意:</strong>未列出的操作将移除限制。",
+ "apihelp-protect-param-expiry": "到期时间戳。如果只有一个时间戳被设置,它将被用于所有保护。使用<kbd>infinite</kbd>、<kbd>indefinite</kbd>、<kbd>infinity</kbd>或<kbd>never</kbd>用于永不过期的保护。",
+ "apihelp-protect-param-reason": "(解除)保护的原因。",
+ "apihelp-protect-param-cascade": "启用级联保护(也就是保护包含于此页面的页面)。如果所有提供的保护等级不支持级联,就将其忽略。",
+ "apihelp-protect-param-watch": "如果设置,就加入已开始(解除)保护的页面至当前用户的监视列表。",
+ "apihelp-protect-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
+ "apihelp-protect-example-protect": "保护一个页面",
+ "apihelp-protect-example-unprotect": "通过设置限制为<kbd>all</kbd>解除保护一个页面。",
+ "apihelp-protect-example-unprotect2": "通过设置没有限制解除保护一个页面",
+ "apihelp-purge-description": "为指定标题刷新缓存。\n\n如果用户尚未登录的话,就需要POST请求。",
+ "apihelp-purge-param-forcelinkupdate": "更新链接表。",
+ "apihelp-purge-param-forcerecursivelinkupdate": "更新链接表中,并更新任何使用此页作为模板的页面的链接表。",
+ "apihelp-purge-example-simple": "刷新<kbd>Main Page</kbd>和<kbd>API</kbd>页面。",
+ "apihelp-purge-example-generator": "刷新主名字空间的前10个页面",
+ "apihelp-query-description": "获取来自和有关MediaWiki的数据。\n\n所有数据修改将首先要使用查询以获得令牌以阻止来自恶意网站的滥用破坏。",
+ "apihelp-query-param-prop": "要为已查询页面获取的属性。",
+ "apihelp-query-param-list": "要获取的列表。",
+ "apihelp-query-param-meta": "要获取的元数据。",
+ "apihelp-query-param-rawcontinue": "目前被忽略。将来<var>$1continue</var>将成为默认,且这里将需要得到原始的<samp>query-continue</samp>数据。",
+ "apihelp-query-example-revisions": "获取<kbd>Main Page</kbd>的[[Special:ApiHelp/query+siteinfo|网站信息]]和[[Special:ApiHelp/query+revisions|修订版本]]。",
+ "apihelp-query-example-allpages": "获取以<kbd>API/</kbd>开头的页面的修订版本",
+ "apihelp-query+allcategories-description": "枚举所有类别。",
+ "apihelp-query+allcategories-param-from": "要作为枚举起始点的类别。",
+ "apihelp-query+allcategories-param-to": "要作为枚举终止点的类别。",
+ "apihelp-query+allcategories-param-prefix": "搜索此值开头的所有分类标题。",
+ "apihelp-query+allcategories-param-dir": "排序方向。",
+ "apihelp-query+allcategories-param-min": "只返回至少带这么多成员的分类。",
+ "apihelp-query+allcategories-param-max": "只返回最多带这么多成员的分类。",
+ "apihelp-query+allcategories-param-limit": "要返回多少个类别。",
+ "apihelp-query+allcategories-param-prop": "要获取的属性:\n;size:在分类中添加页面数。\n;hidden:标记由_&#95;HIDDENCAT_&#95;隐藏的分类。",
+ "apihelp-query+allcategories-example-size": "列出分类及其含有多少页面的信息。",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "只可以与<var>$3user</var>一起使用。",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "不能与<var>$3user</var>一起使用。",
+ "apihelp-query+alldeletedrevisions-param-start": "枚举的起始时间戳。",
+ "apihelp-query+alldeletedrevisions-param-end": "枚举的结束时间戳。",
+ "apihelp-query+alldeletedrevisions-param-from": "从此标题开始列出。",
+ "apihelp-query+alldeletedrevisions-param-to": "列出至此标题为止。",
+ "apihelp-query+alldeletedrevisions-param-tag": "只列出被此标签标记的修订。",
+ "apihelp-query+alldeletedrevisions-param-user": "只列出此用户做出的修订。",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "不要列出此用户做出的修订。",
+ "apihelp-query+alldeletedrevisions-param-namespace": "只列出此名字空间的页面。",
+ "apihelp-query+alldeletedrevisions-param-miser-user-namespace": "<strong>注意:</strong>由于[[mw:Manual:$wgMiserMode|miser模式]],同时使用<var>$1user</var>和<var>$1namespace</var>将导致继续前返回少于<var>$1limit</var>个结果,在极端条件下可能不返回任何结果。",
+ "apihelp-query+alldeletedrevisions-example-user": "列出由<kbd>Example<kbd>作出的最近50次已删除贡献。",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "列出前50次已删除的主名字空间修订。",
+ "apihelp-query+allfileusages-description": "列出所有文件用途,包括不存在的。",
+ "apihelp-query+allfileusages-param-from": "文件的标题开始枚举于.",
+ "apihelp-query+allfileusages-param-prefix": "搜索此值开头的所有文件标题。",
+ "apihelp-query+allfileusages-param-prop": "要包含的信息束:\n;ids:添加使用中的页面的页面ID(不能与$1unique一起使用)。\n;title:添加文件的标题。",
+ "apihelp-query+allfileusages-param-limit": "要返回的总计项目。",
+ "apihelp-query+allfileusages-param-dir": "罗列所采用的方向。",
+ "apihelp-query+allfileusages-example-unique": "列出唯一性的文件标题",
+ "apihelp-query+allfileusages-example-unique-generator": "获取所有文件标题,并标记出缺失者",
+ "apihelp-query+allfileusages-example-generator": "获取包含这些文件的页面",
+ "apihelp-query+allimages-description": "按顺序枚举所有图像。",
+ "apihelp-query+allimages-param-sort": "要作为排序方式的属性。",
+ "apihelp-query+allimages-param-dir": "罗列所采用的方向。",
+ "apihelp-query+allimages-param-minsize": "限于至少这么多字节的图像。",
+ "apihelp-query+allimages-param-maxsize": "限于顶多这么多字节的图像。",
+ "apihelp-query+allimages-param-sha1": "图像的 SHA1 哈希。覆盖$1sha1base36。",
+ "apihelp-query+allimages-param-sha1base36": "基于base 36的图片的SHA1哈希值(用于MediaWiki)。",
+ "apihelp-query+allimages-param-mime": "要搜索的MIME类型,例如<kbd>image/jpeg</kbd>。",
+ "apihelp-query+allimages-param-limit": "共计要返回多少图像。",
+ "apihelp-query+allimages-example-B": "显示以字母<kbd>B</kbd>开始的文件列表。",
+ "apihelp-query+allimages-example-recent": "显示一个最近上传文件的列表,类似[[Special:NewFiles]]。",
+ "apihelp-query+allimages-example-mimetypes": "显示带MIME类型<kbd>image/png</kbd>或<kbd>image/gif</kbd>的文件列表",
+ "apihelp-query+allimages-example-generator": "显示有关4个以<kbd>T</kbd>开头的文件的信息。",
+ "apihelp-query+alllinks-param-namespace": "要列举的名字空间。",
+ "apihelp-query+alllinks-param-limit": "总共要返回多少个项目。",
+ "apihelp-query+alllinks-param-dir": "列出方向。",
+ "apihelp-query+alllinks-example-unique": "列出唯一的链接标题",
+ "apihelp-query+alllinks-example-unique-generator": "获得所有已链接的标题,标记缺少的。",
+ "apihelp-query+alllinks-example-generator": "获取包含这些链接的页面",
+ "apihelp-query+allmessages-description": "返回来自该网站的消息。",
+ "apihelp-query+allmessages-param-messages": "要输出的消息。<kbd>*</kbd>(默认)表示所有消息。",
+ "apihelp-query+allmessages-param-prop": "要获取的属性。",
+ "apihelp-query+allmessages-param-customised": "只返回在此定制情形下的消息。",
+ "apihelp-query+allmessages-param-lang": "返回这种语言的信息。",
+ "apihelp-query+allmessages-param-from": "从此消息开始返回消息。",
+ "apihelp-query+allmessages-param-to": "返回消息至此消息为止。",
+ "apihelp-query+allmessages-param-prefix": "返回带有该前缀的消息。",
+ "apihelp-query+allmessages-example-ipb": "显示以<kbd>ipb-</kbd>开始的消息。",
+ "apihelp-query+allmessages-example-de": "显示德语版的<kbd>august</kbd>和<kbd>mainpage</kbd>消息。",
+ "apihelp-query+allpages-param-namespace": "要列举的名字空间。",
+ "apihelp-query+allpages-param-filterredir": "要列出哪些页面。",
+ "apihelp-query+allpages-param-minsize": "限于至少这么多字节的页面。",
+ "apihelp-query+allpages-param-maxsize": "限于至多这么多字节的页面。",
+ "apihelp-query+allpages-param-prtype": "仅限于受保护页面。",
+ "apihelp-query+allpages-param-limit": "返回的总计页面数。",
+ "apihelp-query+allpages-example-B": "显示以字母<kbd>B</kbd>开头的页面的列表。",
+ "apihelp-query+allpages-example-generator": "显示有关4个以字母<kbd>T</kbd>开头的页面的信息。",
+ "apihelp-query+allpages-example-generator-revisions": "显示前2个以<kbd>Re</kbd>开头的非重定向页面的内容。",
+ "apihelp-query+allredirects-description": "列出至一个名字空间的重定向。",
+ "apihelp-query+allredirects-param-namespace": "要列举的名字空间。",
+ "apihelp-query+allredirects-param-limit": "返回的总计项目数。",
+ "apihelp-query+allredirects-param-dir": "罗列所采用的方向。",
+ "apihelp-query+allredirects-example-unique": "列出孤立目标页面",
+ "apihelp-query+allredirects-example-unique-generator": "获得所有目标页面,标记丢失的",
+ "apihelp-query+allredirects-example-generator": "获得包含重定向的页面",
+ "apihelp-query+alltransclusions-description": "列出所有嵌入页面(使用&#123;&#123;x&#125;&#125;嵌入的页面),包括不存在的。",
+ "apihelp-query+alltransclusions-param-namespace": "要列举的名字空间。",
+ "apihelp-query+alltransclusions-param-limit": "要返回的总计项目。",
+ "apihelp-query+alltransclusions-param-dir": "罗列所采用的方向。",
+ "apihelp-query+alltransclusions-example-unique": "列出孤立嵌入标题",
+ "apihelp-query+alltransclusions-example-generator": "获得包含嵌入内容的页面。",
+ "apihelp-query+allusers-description": "列举所有注册用户。",
+ "apihelp-query+allusers-param-from": "枚举的起始用户名。",
+ "apihelp-query+allusers-param-to": "枚举的结束用户名。",
+ "apihelp-query+allusers-param-prefix": "搜索以此值开始的所有用户。",
+ "apihelp-query+allusers-param-dir": "排序方向。",
+ "apihelp-query+allusers-param-group": "只包含指定组中的用户。",
+ "apihelp-query+allusers-param-excludegroup": "排除指定组中的用户。",
+ "apihelp-query+allusers-param-rights": "仅列出有所选权限的用户。不包括隐性的或自动加入的用户组别(如*、用户或自动确认用户)所授予的权限。",
+ "apihelp-query+allusers-param-limit": "返回的总计用户数。",
+ "apihelp-query+allusers-param-witheditsonly": "只列出有编辑的用户。",
+ "apihelp-query+allusers-param-activeusers": "只列出最近$1天内活跃的用户。",
+ "apihelp-query+allusers-example-Y": "列出以<kbd>Y</kbd>开头的用户。",
+ "apihelp-query+backlinks-description": "查找所有链接至指定页面的页面。",
+ "apihelp-query+backlinks-param-title": "要搜索的标题。不能与<var>$1pageid</var>一起使用。",
+ "apihelp-query+backlinks-param-pageid": "要搜索的页面ID。不能与<var>$1title</var>一起使用。",
+ "apihelp-query+backlinks-param-namespace": "要列举的名字空间。",
+ "apihelp-query+backlinks-param-dir": "罗列所采用的方向。",
+ "apihelp-query+backlinks-param-limit": "返回总计页面数。如果<var>$1redirect</var>被启用,则限定分别适用于每一等级(这意味着将返回多达2 * <var>$1limit</var>个结果)。",
+ "apihelp-query+backlinks-example-simple": "显示至<kbd>Main page<kbd>的链接。",
+ "apihelp-query+backlinks-example-generator": "获得关于链接至<kbd>Main page<kbd>的页面的信息。",
+ "apihelp-query+blocks-description": "列出所有被封禁的用户和IP地址。",
+ "apihelp-query+blocks-param-ids": "要列出的封禁ID列表(可选)。",
+ "apihelp-query+blocks-param-users": "要搜索的用户列表(可选)。",
+ "apihelp-query+blocks-param-prop": "要获取的属性:\n;id:添加封禁ID。\n;user:添加被封禁用户的用户名。\n;userid:添加被封禁用户的用户ID。\n;by:添加执行封禁的用户的用户名。\n;byid:添加执行封禁的用户的用户ID。\n;timestamp:添加封禁生效时的时间戳。\n;expiry:添加封禁截止时的时间戳。\n;reason:添加封禁原因。\n;range:添加受封禁影响的IP地址段。\n;flags:标记编辑禁止(自动封禁、仅限匿名用户等)。",
+ "apihelp-query+blocks-example-simple": "封禁列表",
+ "apihelp-query+blocks-example-users": "列出用户<kbd>Alice</kbd>和<kbd>Bob</kbd>的封禁。",
+ "apihelp-query+categories-description": "页面属于的所有分类列表。",
+ "apihelp-query+categories-param-prop": "要为每个分类获取的额外属性:\n;sortkey:为每个分类添加关键词(十六进制字符串)和关键词前缀(人类可读部分)。\n;timestamp:添加分类添加时的时间戳。\n;hidden:标记由_&#95;HIDDENCAT_&#95;隐藏的分类。",
+ "apihelp-query+categories-param-show": "显示何种分类。",
+ "apihelp-query+categories-param-limit": "返回多少分类。",
+ "apihelp-query+categories-param-dir": "罗列所采用的方向。",
+ "apihelp-query+categories-example-simple": "获取属于<kbd>Albert Einstein</kbd>的分类列表。",
+ "apihelp-query+categories-example-generator": "获得有关用于<kbd>Albert Einstein</kbd>的分类的信息。",
+ "apihelp-query+categoryinfo-description": "返回有关给定分类的信息。",
+ "apihelp-query+categoryinfo-example-simple": "获取有关<kbd>Category:Foo</kbd>和<kbd>Category:Bar</kbd>的信息。",
+ "apihelp-query+categorymembers-description": "在指定的分类中列出所有页面。",
+ "apihelp-query+categorymembers-param-title": "要列举的分类(必需)。必须包括<kbd>{{ns:category}}:</kbd>前缀。不能与<var>$1pageid</var>一起使用。",
+ "apihelp-query+categorymembers-param-pageid": "要枚举的分类的页面 ID。不能与<var>$1title</var>一起使用。",
+ "apihelp-query+categorymembers-param-namespace": "仅包含这些名字空间的页面。注意<kbd>$1type=subcat</kbd>或<kbd>$1type=file</kbd>可能被使用,而不是<kbd>$1namespace=14</kbd>或<kbd>6</kbd>。",
+ "apihelp-query+categorymembers-param-type": "包含的分类成员类型。当<kbd>$1sort=timestamp</kbd>被设置时会忽略。",
+ "apihelp-query+categorymembers-param-limit": "返回页面的最大数量。",
+ "apihelp-query+categorymembers-param-sort": "要作为排序方式的属性。",
+ "apihelp-query+categorymembers-param-dir": "排序的方向。",
+ "apihelp-query+categorymembers-param-start": "开始列举的时间戳。只能与<kbd>$1sort=timestamp</kbd>一起使用。",
+ "apihelp-query+categorymembers-param-end": "列举的结尾时间戳。只能与<kbd>$1sort=timestamp</kbd>一起使用。",
+ "apihelp-query+categorymembers-param-starthexsortkey": "开始列举的关键词,由<kbd>$1prop=sortkey</kbd>返回。不能与<kbd>$1sort=sortkey</kbd>一起使用。",
+ "apihelp-query+categorymembers-param-endhexsortkey": "结束列举的关键词,由<kbd>$1prop=sortkey</kbd>返回。不能与<kbd>$1sort=sortkey</kbd>一起使用。",
+ "apihelp-query+categorymembers-param-startsortkey": "请改用$1starthexsortkey。",
+ "apihelp-query+categorymembers-param-endsortkey": "请改用$1endhexsortkey。",
+ "apihelp-query+categorymembers-example-simple": "获得<kbd>Category:Physics</kbd>中的前10个页面。",
+ "apihelp-query+categorymembers-example-generator": "获得有关<kbd>Category:Physics</kbd>中的前10个页面的页面信息。",
+ "apihelp-query+contributors-description": "获取对一个页面的登录贡献者列表和匿名贡献数。",
+ "apihelp-query+contributors-param-limit": "返回的贡献数。",
+ "apihelp-query+contributors-example-simple": "显示<kbd>Main Page</kbd>的贡献。",
+ "apihelp-query+deletedrevisions-description": "获得删除修订版本信息。\n\n可在很多途径中使用:\n# 获得一组页面的已删除修订,通过设置标题或页面ID。以标题和时间戳排序。\n# 通过设置它们的ID与修订ID获得关于一组已删除修订。以修订ID排序。",
+ "apihelp-query+deletedrevisions-param-tag": "只列出被此标签标记的修订。",
+ "apihelp-query+deletedrevisions-param-user": "只列出此用户做出的修订。",
+ "apihelp-query+deletedrevisions-param-excludeuser": "不要列出此用户做出的修订。",
+ "apihelp-query+deletedrevisions-param-limit": "要列出的修订的最高数额。",
+ "apihelp-query+deletedrevisions-param-prop": "要获取的属性:\n;revid:添加已删除修订的修订ID。\n;parentid:添加上一修订的修订ID至页面中。\n;user:添加做出修订的用户。\n;userid:添加做出修订的用户ID。\n;comment:添加修订的摘要。\n;parsedcomment:添加修订的解析摘要。\n;minor:如果修订为小修订则予以标记。\n;len:添加修订的长度(字节)。\n;sha1:添加修订的SHA-1(base 16)。\n;content:添加修订内容。\n;tags:用于修订的标签。",
+ "apihelp-query+deletedrevisions-example-titles": "列出页面<kbd>Main Page</kbd>和<kbd>Talk:Main Page</kbd>的已删除修订,包含内容。",
+ "apihelp-query+deletedrevisions-example-revids": "列出已删除修订<kbd>123456</kbd>的信息。",
+ "apihelp-query+deletedrevs-paraminfo-modes": "{{PLURAL:$1|模式}}:$2",
+ "apihelp-query+deletedrevs-param-from": "从此标题开始列出。",
+ "apihelp-query+deletedrevs-param-to": "列出至此标题为止。",
+ "apihelp-query+deletedrevs-param-user": "只列出此用户做出的修订。",
+ "apihelp-query+deletedrevs-param-excludeuser": "不要列出此用户做出的修订。",
+ "apihelp-query+deletedrevs-param-namespace": "只列出此名字空间的页面。",
+ "apihelp-query+deletedrevs-example-mode1": "列出最近已删除的对页面<kbd>Main Page</kbd>和<kbd>Talk:Main Page</kbd>的贡献,带内容(模式1)。",
+ "apihelp-query+deletedrevs-example-mode2": "列出由<kbd>Bob</kbd>作出的最近50次已删除贡献(模式2)。",
+ "apihelp-query+deletedrevs-example-mode3-main": "列出前50次主名字空间已删除贡献(模式3)",
+ "apihelp-query+deletedrevs-example-mode3-talk": "列出前50次{{ns:talk}}名字空间已删除页面(模式3):",
+ "apihelp-query+disabled-description": "此查询模块已被禁用。",
+ "apihelp-query+duplicatefiles-param-limit": "返回多少重复文件。",
+ "apihelp-query+duplicatefiles-param-dir": "罗列所采用的方向。",
+ "apihelp-query+duplicatefiles-param-localonly": "只看本地存储库的文件。",
+ "apihelp-query+duplicatefiles-example-simple": "查找与[[:File:Albert Einstein Head.jpg]]重复的文件",
+ "apihelp-query+duplicatefiles-example-generated": "查找所有文件的重复文件",
+ "apihelp-query+embeddedin-param-title": "要搜索的标题。不能与$1pageid一起使用。",
+ "apihelp-query+embeddedin-param-pageid": "要搜索的页面ID。不能与$1title一起使用。",
+ "apihelp-query+embeddedin-param-namespace": "列举的名字空间。",
+ "apihelp-query+embeddedin-param-dir": "罗列所采用的方向。",
+ "apihelp-query+embeddedin-param-filterredir": "如何过滤重定向。",
+ "apihelp-query+embeddedin-param-limit": "返回的总计页面数。",
+ "apihelp-query+embeddedin-example-simple": "显示嵌入<kbd>Template:Stub</kbd>的页面。",
+ "apihelp-query+embeddedin-example-generator": "获得有关显示嵌入<kbd>Template:Stub</kbd>的页面的信息。",
+ "apihelp-query+extlinks-param-limit": "返回多少链接。",
+ "apihelp-query+extlinks-example-simple": "获取<kbd>首页</kbd>的外部链接列表。",
+ "apihelp-query+exturlusage-param-protocol": "URL协议。如果为空并且<var>$1query</var>被设置,协议为<kbd>http</kbd>。将此和<var>$1query</var>都留空以列举所有外部链接。",
+ "apihelp-query+exturlusage-param-query": "不包括协议的搜索字符串。参见[[Special:LinkSearch]]。留空以列出所有外部链接。",
+ "apihelp-query+exturlusage-param-namespace": "要列举的页面名字空间。",
+ "apihelp-query+exturlusage-param-limit": "返回多少页面。",
+ "apihelp-query+exturlusage-example-simple": "显示链接至<kbd>http://www.mediawiki.org</kbd>的页面。",
+ "apihelp-query+filearchive-param-from": "枚举的起始图片标题。",
+ "apihelp-query+filearchive-param-to": "枚举的结束图片标题。",
+ "apihelp-query+filearchive-param-prefix": "搜索所有以此值开头的图像标题。",
+ "apihelp-query+filearchive-param-limit": "返回图像的总数。",
+ "apihelp-query+filearchive-param-dir": "罗列所采用的方向。",
+ "apihelp-query+filearchive-param-sha1": "图片的SHA1哈希值。覆盖$1sha1base36。",
+ "apihelp-query+filearchive-param-sha1base36": "基于base 36的图片的SHA1哈希值(用于MediaWiki)。",
+ "apihelp-query+filearchive-example-simple": "显示已删除文件列表",
+ "apihelp-query+filerepoinfo-example-simple": "获得有关文件存储库的信息。",
+ "apihelp-query+fileusage-param-prop": "要获取的属性:\n;pageid:每个页面的页面ID。\n;title:每个页面的标题。\n;redirect:标记作为重定向的页面。",
+ "apihelp-query+fileusage-param-namespace": "只包括这些名字空间的页面。",
+ "apihelp-query+fileusage-param-limit": "返回多少。",
+ "apihelp-query+fileusage-example-simple": "获取使用[[:File:Example.jpg]]的页面列表",
+ "apihelp-query+fileusage-example-generator": "获取有关使用[[:File:Example.jpg]]的页面的信息",
+ "apihelp-query+imageinfo-description": "返回文件信息和上传历史。",
+ "apihelp-query+imageinfo-param-prop": "要获取的文件信息:",
+ "apihelp-query+imageinfo-paramvalue-prop-timestamp": "添加时间戳至上传的版本。",
+ "apihelp-query+imageinfo-paramvalue-prop-comment": "此版本的摘要。",
+ "apihelp-query+imageinfo-paramvalue-prop-dimensions": "大小别名。",
+ "apihelp-query+imageinfo-paramvalue-prop-sha1": "为文件加入SHA-1哈希值。",
+ "apihelp-query+imageinfo-paramvalue-prop-mime": "添加文件的MIME类型。",
+ "apihelp-query+imageinfo-paramvalue-prop-mediatype": "添加文件媒体类型。",
+ "apihelp-query+imageinfo-param-start": "开始列举的时间戳。",
+ "apihelp-query+imageinfo-param-end": "列举的结束时间戳。",
+ "apihelp-query+imageinfo-param-urlheight": "与$1urlwidth类似。",
+ "apihelp-query+imageinfo-param-metadataversion": "要使用的元数据版本。如果<kbd>latest</kbd>被指定,则使用最新版本。默认为<kbd>1</kbd>以便向下兼容。",
+ "apihelp-query+imageinfo-param-localonly": "只看本地存储库的文件。",
+ "apihelp-query+imageinfo-example-simple": "获取有关[[:File:Albert Einstein Head.jpg]]的当前版本的信息",
+ "apihelp-query+imageinfo-example-dated": "获取有关[[:File:Albert Einstein Head.jpg]]自2008年以来版本的信息",
+ "apihelp-query+images-param-limit": "返回多少文件。",
+ "apihelp-query+images-param-dir": "罗列所采用的方向。",
+ "apihelp-query+images-example-simple": "获取[[首页]]使用的文件列表",
+ "apihelp-query+images-example-generator": "获取有关[[首页]]使用的文件的信息",
+ "apihelp-query+imageusage-description": "查找所有使用指定图片标题的页面。",
+ "apihelp-query+imageusage-param-title": "要搜索的标题。不能与$1pageid一起使用。",
+ "apihelp-query+imageusage-param-pageid": "要搜索的页面ID。不能与$1title一起使用。",
+ "apihelp-query+imageusage-param-namespace": "要列举的名字空间。",
+ "apihelp-query+imageusage-param-dir": "罗列所采用的方向。",
+ "apihelp-query+imageusage-example-simple": "显示使用[[:File:Albert Einstein Head.jpg]]的页面",
+ "apihelp-query+imageusage-example-generator": "获取有关使用[[:File:Albert Einstein Head.jpg]]的页面的信息",
+ "apihelp-query+info-description": "获取基本页面信息。",
+ "apihelp-query+info-param-prop": "要获取的额外属性:",
+ "apihelp-query+info-paramvalue-prop-protection": "列出每个页面的保护等级。",
+ "apihelp-query+info-paramvalue-prop-watched": "列出每个页面的被监视状态。",
+ "apihelp-query+info-paramvalue-prop-watchers": "监视人员数,如果允许。",
+ "apihelp-query+info-paramvalue-prop-readable": "用户是否可以阅读此页面。",
+ "apihelp-query+info-param-testactions": "测试当前用户是否可以在页面上执行某种操作。",
+ "apihelp-query+info-param-token": "请改用[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]。",
+ "apihelp-query+info-example-simple": "获得有关页面<kbd>Main Page</kbd>的信息。",
+ "apihelp-query+info-example-protection": "获取<kbd>首页</kbd>相关的常规和保护信息。",
+ "apihelp-query+iwbacklinks-param-prefix": "跨维基前缀。",
+ "apihelp-query+iwbacklinks-param-title": "要搜索的跨wiki链接。必须与<var>$1blprefix</var>一起使用。",
+ "apihelp-query+iwbacklinks-param-limit": "返回的总计页面数。",
+ "apihelp-query+iwbacklinks-param-prop": "要获取的属性:\n;iwprefix:加入跨wiki前缀。\n;iwtitle:加入跨wiki标题。",
+ "apihelp-query+iwbacklinks-param-dir": "罗列所采用的方向。",
+ "apihelp-query+iwbacklinks-example-simple": "获得链接至[[wikibooks:Test]]的页面。",
+ "apihelp-query+iwbacklinks-example-generator": "获得有关链接至[[wikibooks:Test]]的页面的信息。",
+ "apihelp-query+iwlinks-description": "从指定页面返回所有跨wiki链接。",
+ "apihelp-query+iwlinks-param-url": "是否获取完整URL(不能与$1prop一起使用)。",
+ "apihelp-query+iwlinks-param-limit": "返回多少跨wiki链接。",
+ "apihelp-query+iwlinks-param-prefix": "只返回此前缀的跨wiki链接。",
+ "apihelp-query+iwlinks-param-title": "用于搜索的跨wiki链接。必须与<var>$1prefix</var>一起使用。",
+ "apihelp-query+iwlinks-param-dir": "罗列所采用的方向。",
+ "apihelp-query+iwlinks-example-simple": "从页面<kbd>Main Page</kbd>获得跨wiki链接。",
+ "apihelp-query+langbacklinks-param-lang": "用于语言链接的语言。",
+ "apihelp-query+langbacklinks-param-title": "要搜索的语言链接。必须与$1lang一起使用。",
+ "apihelp-query+langbacklinks-param-limit": "返回的总计页面数。",
+ "apihelp-query+langbacklinks-param-prop": "要获得的属性:\n;lllang:添加语言链接的语言代码。\n;lltitle:添加语言链接的标题。",
+ "apihelp-query+langbacklinks-param-dir": "罗列所采用的方向。",
+ "apihelp-query+langbacklinks-example-simple": "获取链接至[[:fr:Test]]的页面",
+ "apihelp-query+langbacklinks-example-generator": "获取链接至[[:fr:Test]]的页面的信息",
+ "apihelp-query+langlinks-description": "从指定页面返回所有跨语言链接。",
+ "apihelp-query+langlinks-param-limit": "返回多少语言链接。",
+ "apihelp-query+langlinks-param-url": "是否获取完整URL(不能与<var>$1prop</var>一起使用)。",
+ "apihelp-query+langlinks-param-lang": "只返回带此语言代码的语言链接。",
+ "apihelp-query+langlinks-param-title": "要搜索的链接。必须与<var>$1lang</var>一起使用。",
+ "apihelp-query+langlinks-param-dir": "罗列所采用的方向。",
+ "apihelp-query+langlinks-param-inlanguagecode": "本地化语言名称的语言代码。",
+ "apihelp-query+langlinks-example-simple": "从页面<kbd>Main Page</kbd>获得跨语言链接。",
+ "apihelp-query+links-description": "从指定页面返回所有链接。",
+ "apihelp-query+links-param-namespace": "只显示这些名字空间的链接。",
+ "apihelp-query+links-param-limit": "返回多少链接。",
+ "apihelp-query+links-param-dir": "罗列所采用的方向。",
+ "apihelp-query+links-example-simple": "从页面<kbd>Main Page</kbd>获得链接",
+ "apihelp-query+links-example-generator": "获得有关在页面<kbd>Main Page</kbd>中连接的页面的信息。",
+ "apihelp-query+links-example-namespaces": "获得在{{ns:user}}和{{ns:template}}名字空间中来自页面<kbd>Main Page</kbd>的链接。",
+ "apihelp-query+linkshere-description": "查找所有链接至指定页面的页面。",
+ "apihelp-query+linkshere-param-prop": "要获得的属性:\n;pageid:每个页面的页面ID。\n;title:每个页面的标题。\n;redirect:如果页面是一个重定向就标记。",
+ "apihelp-query+linkshere-param-namespace": "只包括这些名字空间的页面。",
+ "apihelp-query+linkshere-param-limit": "返回多少。",
+ "apihelp-query+linkshere-param-show": "只显示符合以下标准的项:\n;redirect:只显示重定向。\n;!redirect:只显示非重定向。",
+ "apihelp-query+linkshere-example-simple": "获取链接至[[首页]]的页面列表",
+ "apihelp-query+linkshere-example-generator": "获取有关链接至[[首页]]的页面的信息",
+ "apihelp-query+logevents-description": "从日志获取事件。",
+ "apihelp-query+logevents-param-start": "枚举的起始时间戳。",
+ "apihelp-query+logevents-param-end": "枚举的结束时间戳。",
+ "apihelp-query+logevents-example-simple": "列出最近日志活动",
+ "apihelp-query+pagepropnames-description": "列出wiki中所有使用中的页面属性名称。",
+ "apihelp-query+pagepropnames-param-limit": "返回名称的最大数量。",
+ "apihelp-query+pagepropnames-example-simple": "获取前10个属性名称。",
+ "apihelp-query+pageprops-example-simple": "获得用于<kbd>Category:Foo</kbd>的属性。",
+ "apihelp-query+pageswithprop-description": "列出所有使用指定页面属性的页面。",
+ "apihelp-query+pageswithprop-param-limit": "返回页面的最大数量。",
+ "apihelp-query+pageswithprop-param-dir": "排序的方向。",
+ "apihelp-query+pageswithprop-example-simple": "列出前10个使用<code>&#123;&#123;DISPLAYTITLE:&#125;&#125;</code>的页面。",
+ "apihelp-query+pageswithprop-example-generator": "获取有关前10个使用<code>_&#95;NOTOC_&#95;</code>的页面的信息。",
+ "apihelp-query+prefixsearch-param-search": "搜索字符串。",
+ "apihelp-query+prefixsearch-param-namespace": "搜索的名字空间。",
+ "apihelp-query+prefixsearch-param-limit": "要返回的结果最大数。",
+ "apihelp-query+prefixsearch-param-offset": "跳过的结果数。",
+ "apihelp-query+prefixsearch-example-simple": "搜索以<kbd>meaning</kbd>开头的页面标题。",
+ "apihelp-query+protectedtitles-param-namespace": "只列出这些名字空间的标题。",
+ "apihelp-query+protectedtitles-param-limit": "返回的总计页面数。",
+ "apihelp-query+protectedtitles-example-simple": "受保护标题列表",
+ "apihelp-query+protectedtitles-example-generator": "找到主命名空间中已保护的标题的链接。",
+ "apihelp-query+querypage-param-limit": "返回的结果数。",
+ "apihelp-query+querypage-example-ancientpages": "返回[[Special:Ancientpages]]的结果。",
+ "apihelp-query+random-param-namespace": "只返回这些名字空间的页面。",
+ "apihelp-query+random-param-limit": "限制返回多少随机页面。",
+ "apihelp-query+random-param-redirect": "加载一个随机重定向而不是一个随机页面。",
+ "apihelp-query+random-example-simple": "从主名字空间返回两个随机页面。",
+ "apihelp-query+recentchanges-description": "枚举最近更改。",
+ "apihelp-query+recentchanges-param-start": "枚举的起始时间戳。",
+ "apihelp-query+recentchanges-param-end": "枚举的结束时间戳。",
+ "apihelp-query+recentchanges-param-user": "只列出此用户的更改。",
+ "apihelp-query+recentchanges-param-excludeuser": "不要列出此用户的更改。",
+ "apihelp-query+recentchanges-param-tag": "只列出带此标签的更改。",
+ "apihelp-query+recentchanges-param-token": "请改用<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>。",
+ "apihelp-query+recentchanges-param-limit": "返回总计更新数。",
+ "apihelp-query+recentchanges-param-type": "显示的更改类型。",
+ "apihelp-query+recentchanges-example-simple": "最近更改列表",
+ "apihelp-query+redirects-description": "返回至指定页面的所有重定向。",
+ "apihelp-query+redirects-param-namespace": "只包含这些名字空间的页面。",
+ "apihelp-query+redirects-param-limit": "返回多少重定向。",
+ "apihelp-query+redirects-example-simple": "获取至[[Project:首页]]的重定向列表",
+ "apihelp-query+redirects-example-generator": "获取所有重定向至[[首页]]的信息",
+ "apihelp-query+revisions-example-content": "获得带内容的数据,用于标题<kbd>API</kbd>和<kbd>Main Page</kbd>的最近修订。",
+ "apihelp-query+revisions-example-last5": "获取<kbd>Main Page</kbd>的最近5次修订。",
+ "apihelp-query+revisions-example-first5": "获取<kbd>Main Page</kbd>的前5次修订。",
+ "apihelp-query+revisions-example-first5-after": "获得<kbd>Main Page</kbd>于2006年05月01日之后做出的前5次修订版本。",
+ "apihelp-query+revisions-example-first5-not-localhost": "获取<kbd>Main Page</kbd>的前5次不是由匿名用户<kbd>127.0.0.1</kbd>做出的修订。",
+ "apihelp-query+revisions-example-first5-user": "获取<kbd>Main Page</kbd>的前5次由用户<kbd>MediaWiki default</kbd>做出的修订。",
+ "apihelp-query+revisions+base-param-limit": "限制返回多少修订。",
+ "apihelp-query+search-param-search": "搜索所有拥有此值的页面标题(或内容)。",
+ "apihelp-query+search-param-namespace": "只在这些名字空间搜索。",
+ "apihelp-query+search-param-info": "要返回的元数据。",
+ "apihelp-query+search-param-limit": "返回的总计页面数。",
+ "apihelp-query+search-param-interwiki": "搜索结果中包含跨wiki结果,如果可用。",
+ "apihelp-query+search-example-simple": "搜索<kbd>meaning</kbd>。",
+ "apihelp-query+search-example-text": "搜索文本<kbd>meaning</kbd>。",
+ "apihelp-query+search-example-generator": "获得有关搜索<kbd>meaning</kbd>返回页面的页面信息。",
+ "apihelp-query+siteinfo-param-numberingroup": "列出用户组中的用户数。",
+ "apihelp-query+siteinfo-example-simple": "获取网站信息",
+ "apihelp-query+siteinfo-example-interwiki": "获取本地跨wiki前缀列表",
+ "apihelp-query+siteinfo-example-replag": "检查当前的响应延迟。",
+ "apihelp-query+stashimageinfo-example-simple": "返回藏匿文件的信息。",
+ "apihelp-query+tags-description": "列出更改标签。",
+ "apihelp-query+tags-param-limit": "列出标签的最大数量。",
+ "apihelp-query+tags-param-prop": "要获取哪个属性:\n;name:添加标签名称。\n;displayname:为标签添加系统消息。\n;description:为标签添加描述。\n;hitcount:已添加此标签的修订版本与日志数量。\n;defined:标识标签是否已定义。\n;source:获得标签来源,它可能包括用于扩展定义的标签的<samp>extension</samp>,以及用于可被用户手动应用的标签的<samp>manual</samp>。\n;active:标签是否仍可被应用。",
+ "apihelp-query+tags-example-simple": "可用标签列表",
+ "apihelp-query+templates-param-namespace": "只显示此名字空间的模板。",
+ "apihelp-query+templates-param-limit": "返回多少模板。",
+ "apihelp-query+templates-param-templates": "只列出这些模板。对于检查某一页面使用某一模板很有用。",
+ "apihelp-query+templates-param-dir": "罗列所采用的方向。",
+ "apihelp-query+templates-example-simple": "获得在页面<kbd>Main Page</kbd>使用的模板。",
+ "apihelp-query+templates-example-generator": "获得有关<kbd>Main Page</kbd>中使用的模板页面的信息。",
+ "apihelp-query+templates-example-namespaces": "获得在{{ns:user}}和{{ns:template}}名字空间中,嵌入在<kbd>Main Page</kbd>页面的页面。",
+ "apihelp-query+tokens-param-type": "要请求的令牌类型。",
+ "apihelp-query+transcludedin-param-namespace": "至包含这些名字空间的页面。",
+ "apihelp-query+transcludedin-param-limit": "返回多少。",
+ "apihelp-query+transcludedin-example-simple": "获得嵌入<kbd>Main Page</kbd>的页面列表。",
+ "apihelp-query+transcludedin-example-generator": "获得有关嵌入<kbd>Main Page</kbd>的页面的信息。",
+ "apihelp-query+usercontribs-description": "获取一位用户的所有编辑。",
+ "apihelp-query+usercontribs-param-limit": "返回贡献的最大数量。",
+ "apihelp-query+usercontribs-param-start": "返回的起始时间戳。",
+ "apihelp-query+usercontribs-param-end": "返回的最终时间戳。",
+ "apihelp-query+usercontribs-param-namespace": "只列出这些名字空间的贡献。",
+ "apihelp-query+usercontribs-example-user": "显示用户<kbd>Example</kbd>的贡献。",
+ "apihelp-query+usercontribs-example-ipprefix": "显示来自<kbd>192.0.2.</kbd>前缀所有 IP 地址的贡献。",
+ "apihelp-query+userinfo-description": "获取有关当前用户的信息。",
+ "apihelp-query+userinfo-example-simple": "获取有关当前用户的信息",
+ "apihelp-query+userinfo-example-data": "获取有关当前用户的额外信息",
+ "apihelp-query+users-description": "获取有关列出用户的信息。",
+ "apihelp-query+users-param-token": "请改用<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>。",
+ "apihelp-query+users-example-simple": "返回用户<kbd>Example</kbd>的信息。",
+ "apihelp-query+watchlist-param-start": "枚举的起始时间戳。",
+ "apihelp-query+watchlist-param-end": "枚举的结束时间戳。",
+ "apihelp-query+watchlist-param-user": "只列出此用户的更改。",
+ "apihelp-query+watchlist-param-excludeuser": "不要列出此用户的更改。",
+ "apihelp-query+watchlist-param-limit": "根据结果返回的结果总数。",
+ "apihelp-query+watchlist-param-token": "允许访问其他用户监视列表的安全密钥(可通过用户的[[Special:Preferences#mw-prefsection-watchlist|参数设置]]找到)。",
+ "apihelp-query+watchlist-example-generator": "在当前用户的监视列表中检索用于最近更改页面的页面信息。",
+ "apihelp-query+watchlistraw-description": "获得当前用户的监视列表上的所有页面。",
+ "apihelp-query+watchlistraw-param-namespace": "只列出指定名字空间的页面。",
+ "apihelp-query+watchlistraw-param-limit": "根据结果返回的结果总数。",
+ "apihelp-query+watchlistraw-param-token": "允许访问其他用户监视列表的安全密钥(可通过用户的[[Special:Preferences#mw-prefsection-watchlist|参数设置]]找到)。",
+ "apihelp-query+watchlistraw-example-simple": "列出当前用户的监视列表中的页面。",
+ "apihelp-revisiondelete-description": "删除和恢复修订版本。",
+ "apihelp-revisiondelete-param-hide": "每次修订要隐藏的东西。",
+ "apihelp-revisiondelete-param-show": "每次修订要恢复显示的东西。",
+ "apihelp-revisiondelete-param-reason": "删除或恢复的原因。",
+ "apihelp-revisiondelete-example-revision": "隐藏<kbd>首页</kbd>的修订版本<kbd>12345</kbd>的内容。",
+ "apihelp-rollback-param-title": "要回退的页面标题。不能与<var>$1pageid</var>一起使用。",
+ "apihelp-rollback-param-pageid": "要回退的页面的页面 ID。不能与<var>$1title</var>一起使用。",
+ "apihelp-rollback-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
+ "apihelp-rollback-example-simple": "回退由用户<kbd>Example</kbd>对<kbd>Main Page</kbd>做出的最近编辑。",
+ "apihelp-rollback-example-summary": "回退由IP用户<kbd>192.0.2.5</kbd>对页面<kbd>Main Page</kbd>做出的最近编辑,带编辑摘要<kbd>Reverting vandalism</kbd>,并将这些编辑和回退标记为机器人编辑。",
+ "apihelp-rsd-description": "导出一个RSD(Really Simple Discovery)架构",
+ "apihelp-rsd-example-simple": "导出RSD架构",
+ "apihelp-setnotificationtimestamp-param-entirewatchlist": "工作于所有已监视页面。",
+ "apihelp-setnotificationtimestamp-example-all": "重置整个监视列表的通知状态。",
+ "apihelp-setnotificationtimestamp-example-pagetimestamp": "设置<kbd>Main page</kbd>的通知时间戳,这样所有从2012年1月1日起的编辑都会是未复核的。",
+ "apihelp-setnotificationtimestamp-example-allpages": "重置在<kbd>{{ns:user}}</kbd>名字空间中的页面的通知状态。",
+ "apihelp-tokens-param-type": "要请求的令牌类型。",
+ "apihelp-unblock-description": "解封一位用户。",
+ "apihelp-unblock-param-id": "解封时需要的封禁ID(通过<kbd>list=blocks</kbd>获得)。不能与<var>$1user</var>一起使用。",
+ "apihelp-unblock-param-user": "要解封的用户名、IP地址或IP段。不能与<var>$1id</var>一起使用。",
+ "apihelp-unblock-param-reason": "解封的原因。",
+ "apihelp-unblock-example-id": "解封封禁ID #<kbd>105</kbd>。",
+ "apihelp-unblock-example-user": "解封用户<kbd>Bob</kbd>,原因<kbd>Sorry Bob</kbd>。",
+ "apihelp-undelete-param-title": "要恢复的页面标题。",
+ "apihelp-undelete-param-reason": "恢复的原因。",
+ "apihelp-undelete-param-fileids": "要恢复的文件修订ID。如果<var>$1timestamps</var>和<var>$1fileids</var>都为空,所有将被恢复。",
+ "apihelp-undelete-example-page": "恢复页面<kbd>Main Page</kbd>。",
+ "apihelp-undelete-example-revisions": "恢复<kbd>首页</kbd>的两个修订。",
+ "apihelp-upload-param-filename": "目标文件名。",
+ "apihelp-upload-param-comment": "上传注释。如果没有指定<var>$1text</var>,那么它也被用于新文件的初始页面文本。",
+ "apihelp-upload-param-watch": "监视页面。",
+ "apihelp-upload-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
+ "apihelp-upload-param-ignorewarnings": "忽略任何警告。",
+ "apihelp-upload-param-file": "文件内容。",
+ "apihelp-upload-param-stash": "如果设置,服务器将临时藏匿文件而不是加入存储库。",
+ "apihelp-upload-param-offset": "块的偏移量(字节)。",
+ "apihelp-upload-param-chunk": "大块内容。",
+ "apihelp-upload-example-url": "从URL上传",
+ "apihelp-upload-example-filekey": "完成一次由于警告而失败的上传。",
+ "apihelp-userrights-description": "更改一位用户的组成员。",
+ "apihelp-userrights-param-user": "用户名。",
+ "apihelp-userrights-param-userid": "用户ID。",
+ "apihelp-userrights-param-add": "将用户加入至这些组中。",
+ "apihelp-userrights-param-remove": "将用户从这些组中移除。",
+ "apihelp-userrights-param-reason": "更改原因。",
+ "apihelp-userrights-example-user": "将用户<kbd>FooBot</kbd>添加至<kbd>bot</kbd>用户组,并从<kbd>sysop</kbd>和<kbd>bureaucrat</kbd>组移除。",
+ "apihelp-userrights-example-userid": "将ID为<kbd>123</kbd>的用户加入至<kbd>机器人</kbd>组,并将其从<kbd>管理员</kbd>和<kbd>行政员</kbd>组移除。",
+ "apihelp-watch-param-title": "要(取消)监视的页面。也可使用<var>$1titles</var>。",
+ "apihelp-watch-example-watch": "监视页面<kbd>Main Page</kbd>。",
+ "apihelp-watch-example-unwatch": "取消监视页面<kbd>首页</kbd>。",
+ "apihelp-format-example-generic": "格式化查询结果为$1格式。",
+ "apihelp-dbg-description": "输出数据为PHP的<code>var_export()</code>格式。",
+ "apihelp-dbgfm-description": "输出数据为PHP的<code>var_export()</code>格式(HTML优质打印效果)。",
+ "apihelp-dump-description": "输出数据为PHP的<code>var_dump()</code>格式。",
+ "apihelp-dumpfm-description": "输出数据为PHP的<code>var_dump()</code>格式(HTML优质打印效果)。",
+ "apihelp-json-description": "输出数据为JSON格式。",
+ "apihelp-jsonfm-description": "输出数据为JSON格式(HTML优质打印效果)。",
+ "apihelp-none-description": "不输出任何东西。",
+ "apihelp-php-description": "输出数据为序列化PHP格式。",
+ "apihelp-phpfm-description": "输出数据为序列化PHP格式(HTML优质打印效果)。",
+ "apihelp-rawfm-description": "输出数据为JSON格式,带调试元素(HTML优质打印效果)。",
+ "apihelp-txt-description": "输出数据为PHP的<code>print_r()</code>格式。",
+ "apihelp-txtfm-description": "输出数据为PHP的<code>print_r()</code>格式(HTML优质打印效果)。",
+ "apihelp-wddx-description": "输出数据为WDDX格式。",
+ "apihelp-wddxfm-description": "输出数据为WDDX格式(HTML优质打印效果)。",
+ "apihelp-xml-description": "输出数据为XML格式。",
+ "apihelp-xml-param-xslt": "如果指定,加入已命名的页面作为一个XSL样式表。值必须是在{{ns:mediawiki}}名字空间以<code>.xsl</code>为结尾的标题。",
+ "apihelp-xmlfm-description": "输出数据为XML格式(HTML优质打印效果)。",
+ "apihelp-yaml-description": "输出数据为YAML格式。",
+ "apihelp-yamlfm-description": "输出数据为YAML格式(HTML优质打印效果)。",
+ "api-format-title": "MediaWiki API 结果",
+ "api-format-prettyprint-header": "这是$1格式的HTML表示。HTML对调试很有用,但不适合应用程序使用。\n\n指定<var>format</var>参数以更改输出格式。要查看$1格式的非HTML表示,设置<kbd>format=$2</kbd>。\n\n参见[[mw:API|完整文档]],或[[Special:ApiHelp/main|API 帮助]]以获取更多信息。",
+ "api-orm-param-props": "要查询的字段。",
+ "api-orm-param-limit": "返回的总行数。",
+ "api-pageset-param-generator": "通过执行指定查询模块获得页面列表以工作。\n\n<strong>注意:</strong>发生器参数名称必须以“g”开头,参见例子。",
+ "api-pageset-param-redirects-nogenerator": "自动解决<var>$1titles</var>、<var>$1pageids</var>和<var>$1revids</var>中的重定向。",
+ "api-help-title": "MediaWiki API 帮助",
+ "api-help-lead": "这是自动生成的MediaWiki API文档页面。\n\n文档和例子:https://www.mediawiki.org/wiki/API:Main_page/zh",
+ "api-help-main-header": "主模块",
+ "api-help-flag-deprecated": "此模块已弃用。",
+ "api-help-flag-internal": "<strong>此模块是内部或不稳定的。</strong>它的操作可以更改而不另行通知。",
+ "api-help-flag-readrights": "此模块需要读取权限。",
+ "api-help-flag-writerights": "此模块需要写入权限。",
+ "api-help-flag-mustbeposted": "此模块只允许POST请求。",
+ "api-help-flag-generator": "此模块可作为发生器使用。",
+ "api-help-parameters": "{{PLURAL:$1|参数}}:",
+ "api-help-param-deprecated": "不推荐使用。",
+ "api-help-param-required": "这个参数是必须的。",
+ "api-help-param-list": "{{PLURAL:$1|1=一个值|2=值(以<kbd>{{!}}</kbd>分隔)}}:$2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=必须为空|可以为空,或$2}}",
+ "api-help-param-limit": "不允许超过$1。",
+ "api-help-param-limit2": "不允许超过$1个(对于机器人则是$2个)。",
+ "api-help-param-integer-min": "{{PLURAL:$1|值}}必须不少于$2。",
+ "api-help-param-integer-max": "{{PLURAL:$1|值}}必须不大于$3。",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|值}}必须介于$2和$3之间。",
+ "api-help-param-multi-separate": "通过“<kbd>|</kbd>”隔开各值。",
+ "api-help-param-multi-max": "值的最高数字是{{PLURAL:$1|$1}}(对于机器人则是{{PLURAL:$2|$2}})。",
+ "api-help-param-default": "默认:$1",
+ "api-help-param-default-empty": "默认:<span class=\"apihelp-empty\">(空)</span>",
+ "api-help-param-token": "从[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]取回的“$1”令牌",
+ "api-help-param-disabled-in-miser-mode": "由于[[mw:Manual:$wgMiserMode|miser模式]]而禁用。",
+ "api-help-param-limited-in-miser-mode": "<strong>注意:</strong>由于[[mw:Manual:$wgMiserMode|miser模式]],使用这个可能导致继续前返回少于<var>$1limit</var>个结果;极端情况下可能不会返回任何结果。",
+ "api-help-param-direction": "列举的方向:\n;newer:最早的优先。注意:$1start应早于$1end。\n;older:最新的优先(默认)。注意:$1start应晚于$1end。",
+ "api-help-param-continue": "当更多结果可用时,使用这个继续。",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(没有说明)</span>",
+ "api-help-examples": "{{PLURAL:$1|例子}}:",
+ "api-help-permissions": "{{PLURAL:$1|权限}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|授予}}:$2",
+ "api-credits-header": "制作人员",
+ "api-credits": "API 开发人员:\n* Roan Kattouw(2007年9月~2009年的开发组领导)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan(创建者,2006年9月~2007年9月的开发组领导)\n* Brad Jorsch(2013年至今的开发组领导)\n\n请将您的评论、建议和问题发送至mediawiki-api@lists.wikimedia.org,或提交错误请求在https://phabricator.wikimedia.org/。"
+}
diff --git a/includes/api/i18n/zh-hant.json b/includes/api/i18n/zh-hant.json
new file mode 100644
index 00000000..dc3cc2d8
--- /dev/null
+++ b/includes/api/i18n/zh-hant.json
@@ -0,0 +1,244 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cwlin0416",
+ "Liuxinyu970226",
+ "LNDDYL",
+ "EagerLin"
+ ]
+ },
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|文件]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 郵件清單]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bug與請求]\n</div>\n<strong>狀態資訊:</strong>本頁所展示的所有功能都應正常工作,但是 API 仍在開發當中,將會隨時變化。請訂閱[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 郵件清單]以便得到更新通知。\n\n<strong>錯誤請求:</strong>當 API 收到錯誤請求時, HTTP header 將會返回一個包含「MediaWiki-API-Error」的值,隨後 header 的值與錯誤碼將會送回並設定為相同的值。詳細資訊請參閱[[mw:API:Errors_and_warnings|API: 錯誤與警告]]。",
+ "apihelp-main-param-action": "要執行的動作。",
+ "apihelp-main-param-format": "輸出的格式。",
+ "apihelp-block-description": "封鎖使用者。",
+ "apihelp-block-param-user": "您要封鎖的使用者名稱、IP 位址或 IP 範圍。",
+ "apihelp-block-param-reason": "封鎖原因。",
+ "apihelp-block-param-anononly": "僅封鎖匿名使用者 (禁止這個 IP 位址的匿名使用者編輯)。",
+ "apihelp-block-param-nocreate": "禁止建立帳號。",
+ "apihelp-block-param-autoblock": "自動封鎖最後使用的 IP 位址,以及在這之後嘗試登入的 IP 位址。",
+ "apihelp-block-param-noemail": "禁止使用者透過 Wiki 寄送電子郵件。 (需要 <code>blockemail</code> 權限)。",
+ "apihelp-block-param-hidename": "隱藏封鎖日誌的使用者名稱。 (需要 <code>hideuser</code> 權限)。",
+ "apihelp-block-param-allowusertalk": "允許使用者編輯自己的對話頁面 (依據 <var>[[mw:Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var> 的設定)。",
+ "apihelp-block-param-reblock": "若使用者已被封鎖,覆寫既有的封鎖設定值。",
+ "apihelp-block-param-watchuser": "監視使用者或 IP 位址的使用者頁面與對話頁面。",
+ "apihelp-block-example-ip-simple": "封鎖 IP 位址 <kbd>192.0.2.5</kbd> 三天,原因為 <kbd>First strike</kbd>。",
+ "apihelp-block-example-user-complex": "永久封鎖 IP 位址 <kbd>Vandal</kbd>,原因為 <kbd>First strike</kbd>。",
+ "apihelp-checktoken-description": "檢查來自 <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> 的密鑰有效性。",
+ "apihelp-checktoken-param-type": "要測試的密鑰類型。",
+ "apihelp-checktoken-param-token": "要測試的密鑰。",
+ "apihelp-checktoken-param-maxtokenage": "密鑰的有效期間,以秒為單位。",
+ "apihelp-checktoken-example-simple": "測試 <kbd>csrf</kbd> 密鑰的有效性。",
+ "apihelp-clearhasmsg-description": "清除目前使用者的 <code>hasmsg</code> 標記。",
+ "apihelp-clearhasmsg-example-1": "清除目前使用者的 <code>hasmsg</code> 標記。",
+ "apihelp-compare-description": "比較 2 個頁面間的差異。\n\n\"from\" 以及 \"to\" 的修訂編號,頁面標題或頁面 ID 為必填。",
+ "apihelp-compare-param-fromtitle": "要比對的第一個標題。",
+ "apihelp-compare-param-fromid": "要比對的第一個頁面 ID。",
+ "apihelp-compare-param-fromrev": "要比對的第一個修訂。",
+ "apihelp-compare-param-totitle": "要比對的第二個標題。",
+ "apihelp-compare-param-toid": "要比對的第二個頁面 ID。",
+ "apihelp-compare-param-torev": "要比對的第二個修訂。",
+ "apihelp-compare-example-1": "建立修訂 1 與 1 的差異檔",
+ "apihelp-createaccount-description": "建立新使用者帳號。",
+ "apihelp-createaccount-param-name": "使用者名稱。",
+ "apihelp-createaccount-param-password": "密碼 (若有設定 <var>$1mailpassword</var> 則可略過)。",
+ "apihelp-createaccount-param-domain": "外部認証使用的網域 (選填)。",
+ "apihelp-createaccount-param-token": "已取得帳號建立密鑰於第一次請求。",
+ "apihelp-createaccount-param-email": "使用者的電子郵件地址 (選填) 。",
+ "apihelp-createaccount-param-realname": "使用者的真實姓名 (選填)。",
+ "apihelp-createaccount-param-mailpassword": "若設為其他值,將會以電子郵件寄送隨機密碼給使用者。",
+ "apihelp-createaccount-param-reason": "建立帳號時選填的原因,會被記錄到日誌當中。",
+ "apihelp-createaccount-param-language": "要設定的使用者預設語言代碼 (選填,預設依據內容語言)。",
+ "apihelp-createaccount-example-pass": "建立使用者 <kbd>testuser</kbd> 使用密碼 <kbd>test123</kbd>",
+ "apihelp-createaccount-example-mail": "建立使用者 <kbd>testmailuser</kbd> 並且電子郵件通知隨機產生的密碼。",
+ "apihelp-delete-description": "刪除頁面。",
+ "apihelp-delete-param-title": "您欲刪除的頁面標題。 無法與 <var>$1pageid</var> 同時使用。",
+ "apihelp-delete-param-pageid": "您欲刪除頁面的頁面 ID。 無法與 <var>$1title</var> 同時使用。",
+ "apihelp-delete-param-reason": "刪除的原因。 若未設定,將會使用自動產生的原因。",
+ "apihelp-delete-param-watch": "加入目前頁面至您的監視清單。",
+ "apihelp-delete-param-watchlist": "無條件使用設置將頁面加入或移除目前使用者的監視清單或者是不更改監視清單。",
+ "apihelp-delete-param-unwatch": "從您的監視清單中移除目前頁面。",
+ "apihelp-delete-param-oldimage": "由 [[Special:ApiHelp/query+imageinfo|action=query&prop=imageinfo&iiprop=archivename]] 所提供要刪除的舊圖片名稱。",
+ "apihelp-delete-example-simple": "刪除 <kbd>Main Page</kbd>。",
+ "apihelp-delete-example-reason": "刪除 <kbd>Main Page</kbd> 原因為 <kbd>Preparing for move</kbd>。",
+ "apihelp-disabled-description": "已停用此模組。",
+ "apihelp-edit-description": "建立與編輯頁面。",
+ "apihelp-edit-param-title": "您欲編輯的頁面標題。 無法與 <var>$1pageid</var> 同時使用。",
+ "apihelp-edit-param-pageid": "您欲編輯頁面的頁面 ID。 無法與 <var>$1title</var> 同時使用。",
+ "apihelp-edit-param-section": "章節編號。 <kbd>0</kbd> 代表最上層章節,<kbd>new</kbd> 代表新章節。",
+ "apihelp-edit-param-sectiontitle": "新章節的標題。",
+ "apihelp-edit-param-text": "頁面內容。",
+ "apihelp-edit-param-summary": "編輯摘要。 當未設定 $1section=new 與 $1sectiontitle 時也會當做章節標題。",
+ "apihelp-edit-param-minor": "小編輯。",
+ "apihelp-edit-param-notminor": "非小編輯。",
+ "apihelp-edit-param-bot": "標記此編輯為機器人編輯。",
+ "apihelp-edit-param-basetimestamp": "基於修訂的時間戳記,用來檢測編輯衝突。也许可以取得[[Special:ApiHelp/query+revisions|action=query&prop=revisions&rvprop=timestamp]]認可。",
+ "apihelp-edit-param-createonly": "若頁面已存在,則不編輯頁面。",
+ "apihelp-edit-param-nocreate": "若頁面不存在,則產生錯誤。",
+ "apihelp-edit-param-watch": "加入目前頁面至您的監視清單。",
+ "apihelp-edit-param-unwatch": "從您的監視清單中移除目前頁面。",
+ "apihelp-edit-example-edit": "編輯頁面",
+ "apihelp-emailuser-description": "寄送電子郵件給使用者。",
+ "apihelp-emailuser-param-target": "電子郵件的收件使用者。",
+ "apihelp-emailuser-param-subject": "郵件主旨。",
+ "apihelp-emailuser-param-text": "郵件內容。",
+ "apihelp-emailuser-param-ccme": "寄送一份此郵件的複本給我。",
+ "apihelp-emailuser-example-email": "寄送電子郵件給使用者 <kbd>WikiSysop</kbd> 使用內容 <kbd>Content</kbd>",
+ "apihelp-expandtemplates-description": "展開所有於 wikitext 中模板。",
+ "apihelp-expandtemplates-param-title": "頁面標題。",
+ "apihelp-expandtemplates-param-text": "要轉換的 Wikitext。",
+ "apihelp-feedcontributions-param-showsizediff": "顯示修訂版本之間的差異大小。",
+ "apihelp-feedcontributions-example-simple": "返回使用者<kbd>Example</kbd>的貢獻。",
+ "apihelp-feedrecentchanges-description": "返回近期邊更摘要。",
+ "apihelp-feedrecentchanges-param-feedformat": "摘要格式。",
+ "apihelp-feedrecentchanges-param-namespace": "用於限制結果的命名空間。",
+ "apihelp-feedrecentchanges-param-invert": "除所選定者外的所有命名空間。",
+ "apihelp-feedrecentchanges-param-limit": "回傳的結果數量上限。",
+ "apihelp-feedrecentchanges-param-hideminor": "隱藏小編輯。",
+ "apihelp-feedrecentchanges-param-hidebots": "隱藏由機器人做的變更。",
+ "apihelp-feedrecentchanges-param-hideanons": "隱藏匿名使用者做的變更。",
+ "apihelp-feedrecentchanges-param-hideliu": "隱藏已註冊使用者做的變更。",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "隱藏已巡查的變更。",
+ "apihelp-feedrecentchanges-example-simple": "顯示近期變動",
+ "apihelp-feedrecentchanges-example-30days": "顯示近期30天內的變動",
+ "apihelp-feedwatchlist-description": "返回監視清單 feed。",
+ "apihelp-feedwatchlist-param-feedformat": "Feed 的格式。",
+ "apihelp-filerevert-param-comment": "上載意見。",
+ "apihelp-help-example-main": "主模組使用說明",
+ "apihelp-help-example-recursive": "一個頁面中的所有說明。",
+ "apihelp-help-example-help": "說明模組自身的說明。",
+ "apihelp-imagerotate-description": "旋轉一張或多張圖片。",
+ "apihelp-import-param-summary": "匯入摘要。",
+ "apihelp-import-param-xml": "上載的 XML 檔。",
+ "apihelp-import-param-interwikisource": "用於跨 wiki 匯入:匯入的來源 wiki。",
+ "apihelp-import-param-interwikipage": "用於跨 wiki 匯入:匯入的頁面。",
+ "apihelp-import-param-fullhistory": "用於跨 wiki 匯入:完整匯入歷史,而不只是最新版本。",
+ "apihelp-import-param-templates": "用於跨 wiki 匯入:匯入一切包含的模板。",
+ "apihelp-import-param-namespace": "用於跨 wiki 匯入:匯入至此命名空間。",
+ "apihelp-import-param-rootpage": "匯入作為此頁面的子頁面。",
+ "apihelp-login-param-name": "使用者名稱。",
+ "apihelp-login-param-password": "密碼。",
+ "apihelp-login-param-domain": "網域名稱(可選)。",
+ "apihelp-login-example-login": "登入",
+ "apihelp-logout-description": "登出並清除 session 資料。",
+ "apihelp-logout-example-logout": "登出當前使用者",
+ "apihelp-move-description": "移動頁面。",
+ "apihelp-move-param-from": "重新命名本頁面的標題。不能與 <var>$1fromid</var> 一起出現。",
+ "apihelp-move-param-fromid": "重新命名本頁面的 ID 。不能與 <var>$1fromid</var> 一起出現。",
+ "apihelp-move-param-to": "將本頁面的標題重新命名為",
+ "apihelp-move-param-reason": "重新命名的原因。",
+ "apihelp-move-param-movesubpages": "如果適用,則重新命名子頁面。",
+ "apihelp-move-param-noredirect": "不要建立重新導向。",
+ "apihelp-move-param-ignorewarnings": "忽略所有警告。",
+ "apihelp-opensearch-description": "使用 OpenSearch 協定搜尋本 wiki。",
+ "apihelp-opensearch-param-search": "搜尋字串。",
+ "apihelp-opensearch-param-limit": "回傳的結果數量上限。",
+ "apihelp-opensearch-param-namespace": "搜尋的命名空間。",
+ "apihelp-opensearch-param-format": "輸出的格式。",
+ "apihelp-options-example-reset": "重設所有偏好設定",
+ "apihelp-query+allcategories-param-limit": "要回傳的分類數量。",
+ "apihelp-query+allfileusages-param-limit": "要回傳的項目總數。",
+ "apihelp-query+allimages-param-limit": "要回傳的圖片總數。",
+ "apihelp-query+alllinks-param-limit": "要回傳的項目總數。",
+ "apihelp-query+allpages-param-limit": "要回傳的頁面總數。",
+ "apihelp-query+allredirects-param-limit": "要回傳的項目總數。",
+ "apihelp-query+alltransclusions-param-limit": "要回傳的項目總數。",
+ "apihelp-query+categories-param-limit": "要回傳的分類數量。",
+ "apihelp-query+categorymembers-param-limit": "回傳的頁面數量上限。",
+ "apihelp-query+contributors-param-limit": "要回傳的貢獻人員數量。",
+ "apihelp-query+duplicatefiles-param-limit": "要回傳的重複檔案數量。",
+ "apihelp-query+embeddedin-param-limit": "要回傳的頁面總數。",
+ "apihelp-query+extlinks-param-limit": "要回傳的連結數量。",
+ "apihelp-query+exturlusage-param-limit": "要回傳的頁面數量。",
+ "apihelp-query+filearchive-param-limit": "要回傳的圖片總數。",
+ "apihelp-query+fileusage-param-limit": "要回傳的數量。",
+ "apihelp-query+imageinfo-param-limit": "每個檔案要回傳的檔案修訂數量。",
+ "apihelp-query+images-param-limit": "要回傳的檔案數量。",
+ "apihelp-query+iwlinks-param-limit": "要回傳的跨 Wiki 連結數量。",
+ "apihelp-query+langbacklinks-param-limit": "要回傳的頁面總數。",
+ "apihelp-query+langlinks-param-limit": "要回傳的 langlinks 數量。",
+ "apihelp-query+links-param-limit": "要回傳的連結數量。",
+ "apihelp-query+linkshere-param-limit": "要回傳的數量。",
+ "apihelp-query+logevents-param-limit": "要回傳的事件項目總數。",
+ "apihelp-query+pagepropnames-param-limit": "回傳的名稱數量上限。",
+ "apihelp-query+pageswithprop-param-limit": "回傳的頁面數量上限。",
+ "apihelp-query+prefixsearch-param-limit": "回傳的結果數量上限。",
+ "apihelp-query+protectedtitles-param-limit": "要回傳的頁面總數。",
+ "apihelp-query+querypage-param-limit": "回傳的結果數量。",
+ "apihelp-query+recentchanges-description": "列舉出近期變動。",
+ "apihelp-query+recentchanges-param-limit": "要回傳變更總數。",
+ "apihelp-query+recentchanges-example-simple": "近期變動清單",
+ "apihelp-query+redirects-param-limit": "要回傳的重新導向數量。",
+ "apihelp-query+search-param-limit": "要回傳的頁面總數。",
+ "apihelp-query+templates-param-limit": "要回傳的樣板數量。",
+ "apihelp-query+tokens-param-type": "要請求的密鑰類型。",
+ "apihelp-query+tokens-example-simple": "接收 csrf 密鑰 (預設)。",
+ "apihelp-query+tokens-example-types": "接收監視密鑰以及巡邏密鑰。",
+ "apihelp-query+transcludedin-param-limit": "回傳的數量。",
+ "apihelp-query+usercontribs-param-limit": "回傳的貢獻數量上限。",
+ "apihelp-query+watchlist-param-limit": "每個請求要回傳的結果總數。",
+ "apihelp-query+watchlistraw-param-limit": "每個請求要回傳的結果總數。",
+ "apihelp-tokens-description": "取得資料修改動作的密鑰。\n\n此模組已因支援 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] 而停用。",
+ "apihelp-unblock-param-reason": "解除封鎖的原因。",
+ "apihelp-unblock-example-id": "解除封銷 ID #<kbd>105</kbd>。",
+ "apihelp-undelete-param-reason": "還原的原因。",
+ "apihelp-userrights-description": "更改一位使用者的群組成員。",
+ "apihelp-userrights-param-user": "使用者名稱。",
+ "apihelp-userrights-param-userid": "使用者 ID。",
+ "apihelp-userrights-param-add": "加入使用者至這些群組。",
+ "apihelp-userrights-param-remove": "從這些群組移除使用者。",
+ "apihelp-userrights-param-reason": "變更的原因。",
+ "apihelp-format-example-generic": "格式化查詢結果為 $1 格式",
+ "apihelp-dbg-description": "使用 PHP 的 <code>var_export()</code> 格式輸出資料。",
+ "apihelp-dbgfm-description": "使用 PHP 的 <code>var_export()</code> 格式輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-dump-description": "使用 PHP 的 <code>var_dump()</code> 格式輸出資料。",
+ "apihelp-dumpfm-description": "使用 PHP 的 <code>var_dump()</code> 格式輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-json-description": "使用 JSON 格式輸出資料。",
+ "apihelp-jsonfm-description": "使用 JSON 格式輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-none-description": "不輸出。",
+ "apihelp-php-description": "使用序列化 PHP 格式輸出資料。",
+ "apihelp-phpfm-description": "使用序列化 PHP 格式輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-rawfm-description": "使用 JSON 格式的除錯元素輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-txt-description": "使用 PHP 的 <code>print_r()</code> 格式輸出資料。",
+ "apihelp-txtfm-description": "使用 PHP 的 <code>print_r()</code> 格式輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-wddx-description": "使用 WDDX 格式輸出資料。",
+ "apihelp-wddxfm-description": "使用 WDDX 格式輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-xml-description": "使用 XML 格式輸出資料。",
+ "apihelp-xmlfm-description": "使用 XML 格式輸出資料 (使用 HTML 格式顯示)。",
+ "apihelp-yaml-description": "使用 YAML 格式輸出資料。",
+ "apihelp-yamlfm-description": "使用 YAML 格式輸出資料 (使用 HTML 格式顯示)。",
+ "api-format-title": "MediaWiki API 結果",
+ "api-orm-param-props": "要查詢的欄位。",
+ "api-orm-param-limit": "回傳的列數上限。",
+ "api-pageset-param-titles": "要使用的標題清單。",
+ "api-pageset-param-pageids": "要使用的頁面 ID 清單。",
+ "api-pageset-param-revids": "要使用的修訂 ID 清單。",
+ "api-help-title": "MediaWiki API 說明",
+ "api-help-lead": "此頁為自動產生的 MediaWiki API 說明文件頁面。\n\n說明文件與範例:https://www.mediawiki.org/wiki/API",
+ "api-help-main-header": "主要模組",
+ "api-help-flag-deprecated": "此模組已停用。",
+ "api-help-flag-readrights": "此模組需要讀取權限。",
+ "api-help-flag-writerights": "此模組需要寫入權限。",
+ "api-help-flag-mustbeposted": "此模組僅接受 POST 請求。",
+ "api-help-parameters": "{{PLURAL:$1|參數}}:",
+ "api-help-param-deprecated": "已停用。",
+ "api-help-param-required": "此參數為必填。",
+ "api-help-param-list": "{{PLURAL:$1|1=單值|2=多值 (以 <kbd>{{!}}</kbd> 分隔)}}:$2",
+ "api-help-param-list-can-be-empty": "{{PLURAL:$1|0=必須空白|可以空白,或 $2}}",
+ "api-help-param-limit": "不允許超過 $1。",
+ "api-help-param-limit2": "不允許超過 $1 (機器人為 $2)。",
+ "api-help-param-integer-min": "{{PLURAL:$1|1=數值|2=數值}}不可小於 $2。",
+ "api-help-param-integer-max": "{{PLURAL:$1|1=數值|2=數值}}不可大於 $3。",
+ "api-help-param-integer-minmax": "{{PLURAL:$1|1=數值|2=數值}}必須在 $2 與 $3 之間。",
+ "api-help-param-upload": "必須使用 multipart/form-data 以檔案上傳的方式傳送。",
+ "api-help-param-multi-separate": "使用 <kbd>|</kbd> 分隔數值。",
+ "api-help-param-multi-max": "上限值為 {{PLURAL:$1|$1}} (機器人為 {{PLURAL:$2|$2}})。",
+ "api-help-param-default": "預設值:$1",
+ "api-help-param-default-empty": "預設值:<span class=\"apihelp-empty\">(空)</span>",
+ "api-help-param-token": "自 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] 接收的 \"$1\" 密鑰。",
+ "api-help-param-no-description": "<span class=\"apihelp-empty\">(無描述)</span>",
+ "api-help-examples": "{{PLURAL:$1|範例}}:",
+ "api-help-permissions": "{{PLURAL:$1|權限}}:",
+ "api-help-permissions-granted-to": "{{PLURAL:$1|已授權給}}: $2",
+ "api-credits-header": "製作群",
+ "api-credits": "API 開發人員:\n* Roan Kattouw (首席開發者 Sep 2007–2009)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Yuri Astrakhan (創立者,首席開發者 Sep 2006–Sep 2007)\n* Brad Jorsch (首席開發者 2013–present)\n\n請傳送您的評論、建議以及問題至 mediawiki-api@lists.wikimedia.org\n或者回報問題至 https://phabricator.wikimedia.org/。"
+}
diff --git a/includes/cache/BacklinkCache.php b/includes/cache/BacklinkCache.php
index ed62bba0..10b4fb00 100644
--- a/includes/cache/BacklinkCache.php
+++ b/includes/cache/BacklinkCache.php
@@ -38,8 +38,6 @@
* of memory.
*
* Introduced by r47317
- *
- * @internal documentation reviewed on 18 Mar 2011 by hashar
*/
class BacklinkCache {
/** @var ProcessCacheLRU */
@@ -176,7 +174,6 @@ class BacklinkCache {
* @return ResultWrapper
*/
protected function queryLinks( $table, $startId, $endId, $max, $select = 'all' ) {
- wfProfileIn( __METHOD__ );
$fromField = $this->getPrefix( $table ) . '_from';
@@ -231,8 +228,6 @@ class BacklinkCache {
}
}
- wfProfileOut( __METHOD__ );
-
return $res;
}
@@ -255,7 +250,7 @@ class BacklinkCache {
return $prefixes[$table];
} else {
$prefix = null;
- wfRunHooks( 'BacklinkCacheGetPrefix', array( $table, &$prefix ) );
+ Hooks::run( 'BacklinkCacheGetPrefix', array( $table, &$prefix ) );
if ( $prefix ) {
return $prefix;
} else {
@@ -303,7 +298,7 @@ class BacklinkCache {
break;
default:
$conds = null;
- wfRunHooks( 'BacklinkCacheGetConditions', array( $table, $this->title, &$conds ) );
+ Hooks::run( 'BacklinkCacheGetConditions', array( $table, $this->title, &$conds ) );
if ( !$conds ) {
throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
}
@@ -490,4 +485,55 @@ class BacklinkCache {
return array( 'numRows' => $numRows, 'batches' => $batches );
}
+
+ /**
+ * Get a Title iterator for cascade-protected template/file use backlinks
+ *
+ * @return TitleArray
+ * @since 1.25
+ */
+ public function getCascadeProtectedLinks() {
+ $dbr = $this->getDB();
+
+ // @todo: use UNION without breaking tests that use temp tables
+ $resSets = array();
+ $resSets[] = $dbr->select(
+ array( 'templatelinks', 'page_restrictions', 'page' ),
+ array( 'page_namespace', 'page_title', 'page_id' ),
+ array(
+ 'tl_namespace' => $this->title->getNamespace(),
+ 'tl_title' => $this->title->getDBkey(),
+ 'tl_from = pr_page',
+ 'pr_cascade' => 1,
+ 'page_id = tl_from'
+ ),
+ __METHOD__,
+ array( 'DISTINCT' )
+ );
+ if ( $this->title->getNamespace() == NS_FILE ) {
+ $resSets[] = $dbr->select(
+ array( 'imagelinks', 'page_restrictions', 'page' ),
+ array( 'page_namespace', 'page_title', 'page_id' ),
+ array(
+ 'il_to' => $this->title->getDBkey(),
+ 'il_from = pr_page',
+ 'pr_cascade' => 1,
+ 'page_id = il_from'
+ ),
+ __METHOD__,
+ array( 'DISTINCT' )
+ );
+ }
+
+ // Combine and de-duplicate the results
+ $mergedRes = array();
+ foreach ( $resSets as $res ) {
+ foreach ( $res as $row ) {
+ $mergedRes[$row->page_id] = $row;
+ }
+ }
+
+ return TitleArray::newFromResult(
+ new FakeResultWrapper( array_values( $mergedRes ) ) );
+ }
}
diff --git a/includes/cache/CacheDependency.php b/includes/cache/CacheDependency.php
index 9b48ecb7..517f3798 100644
--- a/includes/cache/CacheDependency.php
+++ b/includes/cache/CacheDependency.php
@@ -181,13 +181,11 @@ class FileDependency extends CacheDependency {
function loadDependencyValues() {
if ( is_null( $this->timestamp ) ) {
- if ( !file_exists( $this->filename ) ) {
- # Dependency on a non-existent file
- # This is a valid concept!
- $this->timestamp = false;
- } else {
- $this->timestamp = filemtime( $this->filename );
- }
+ wfSuppressWarnings();
+ # Dependency on a non-existent file stores "false"
+ # This is a valid concept!
+ $this->timestamp = filemtime( $this->filename );
+ wfRestoreWarnings();
}
}
@@ -195,7 +193,10 @@ class FileDependency extends CacheDependency {
* @return bool
*/
function isExpired() {
- if ( !file_exists( $this->filename ) ) {
+ wfSuppressWarnings();
+ $lastmod = filemtime( $this->filename );
+ wfRestoreWarnings();
+ if ( $lastmod === false ) {
if ( $this->timestamp === false ) {
# Still nonexistent
return false;
@@ -206,7 +207,6 @@ class FileDependency extends CacheDependency {
return true;
}
} else {
- $lastmod = filemtime( $this->filename );
if ( $lastmod > $this->timestamp ) {
# Modified or created
wfDebug( "Dependency triggered: {$this->filename} changed.\n" );
diff --git a/includes/cache/HTMLFileCache.php b/includes/cache/HTMLFileCache.php
index 58ca2dcd..c07032bf 100644
--- a/includes/cache/HTMLFileCache.php
+++ b/includes/cache/HTMLFileCache.php
@@ -131,7 +131,7 @@ class HTMLFileCache extends FileCacheBase {
return false;
}
// Allow extensions to disable caching
- return wfRunHooks( 'HTMLFileCache::useFileCache', array( $context ) );
+ return Hooks::run( 'HTMLFileCache::useFileCache', array( $context ) );
}
/**
diff --git a/includes/cache/LinkBatch.php b/includes/cache/LinkBatch.php
index 48c063f4..77e4d490 100644
--- a/includes/cache/LinkBatch.php
+++ b/includes/cache/LinkBatch.php
@@ -128,11 +128,9 @@ class LinkBatch {
* @return array Remaining IDs
*/
protected function executeInto( &$cache ) {
- wfProfileIn( __METHOD__ );
$res = $this->doQuery();
$this->doGenderQuery();
$ids = $this->addResultToCache( $cache, $res );
- wfProfileOut( __METHOD__ );
return $ids;
}
@@ -185,7 +183,6 @@ class LinkBatch {
if ( $this->isEmpty() ) {
return false;
}
- wfProfileIn( __METHOD__ );
// This is similar to LinkHolderArray::replaceInternal
$dbr = wfGetDB( DB_SLAVE );
@@ -205,7 +202,6 @@ class LinkBatch {
$caller .= " (for {$this->caller})";
}
$res = $dbr->select( $table, $fields, $conds, $caller );
- wfProfileOut( __METHOD__ );
return $res;
}
diff --git a/includes/cache/LinkCache.php b/includes/cache/LinkCache.php
index 6925df90..eace1eea 100644
--- a/includes/cache/LinkCache.php
+++ b/includes/cache/LinkCache.php
@@ -216,25 +216,20 @@ class LinkCache {
* @return int
*/
public function addLinkObj( $nt ) {
- global $wgAntiLockFlags, $wgContentHandlerUseDB;
-
- wfProfileIn( __METHOD__ );
+ global $wgContentHandlerUseDB;
$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;
}
@@ -242,14 +237,8 @@ class LinkCache {
# Some fields heavily used for linking...
if ( $this->mForUpdate ) {
$db = wfGetDB( DB_MASTER );
- if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) {
- $options = array( 'FOR UPDATE' );
- } else {
- $options = array();
- }
} else {
$db = wfGetDB( DB_SLAVE );
- $options = array();
}
$f = array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
@@ -259,7 +248,7 @@ class LinkCache {
$s = $db->selectRow( 'page', $f,
array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
- __METHOD__, $options );
+ __METHOD__ );
# Set fields...
if ( $s !== false ) {
$this->addGoodLinkObjFromRow( $nt, $s );
@@ -269,8 +258,6 @@ class LinkCache {
$id = 0;
}
- wfProfileOut( __METHOD__ );
-
return $id;
}
diff --git a/includes/cache/LocalisationCache.php b/includes/cache/LocalisationCache.php
index ae27fba3..dc5a2eb6 100644
--- a/includes/cache/LocalisationCache.php
+++ b/includes/cache/LocalisationCache.php
@@ -20,6 +20,10 @@
* @file
*/
+use Cdb\Exception as CdbException;
+use Cdb\Reader as CdbReader;
+use Cdb\Writer as CdbWriter;
+
/**
* Class for caching the contents of localisation files, Messages*.php
* and *.i18n.php.
@@ -33,7 +37,7 @@
* as grammatical transformation, is done by the caller.
*/
class LocalisationCache {
- const VERSION = 2;
+ const VERSION = 3;
/** Configuration associative array */
private $conf;
@@ -253,9 +257,7 @@ class LocalisationCache {
*/
public function getItem( $code, $key ) {
if ( !isset( $this->loadedItems[$code][$key] ) ) {
- wfProfileIn( __METHOD__ . '-load' );
$this->loadItem( $code, $key );
- wfProfileOut( __METHOD__ . '-load' );
}
if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
@@ -276,9 +278,7 @@ class LocalisationCache {
if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
!isset( $this->loadedItems[$code][$key] )
) {
- wfProfileIn( __METHOD__ . '-load' );
$this->loadSubitem( $code, $key, $subkey );
- wfProfileOut( __METHOD__ . '-load' );
}
if ( isset( $this->data[$code][$key][$subkey] ) ) {
@@ -505,7 +505,6 @@ class LocalisationCache {
* @return array
*/
protected function readPHPFile( $_fileName, $_fileType ) {
- wfProfileIn( __METHOD__ );
// Disable APC caching
wfSuppressWarnings();
$_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
@@ -522,10 +521,8 @@ class LocalisationCache {
} elseif ( $_fileType == 'aliases' ) {
$data = compact( 'aliases' );
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
}
- wfProfileOut( __METHOD__ );
return $data;
}
@@ -537,24 +534,20 @@ class LocalisationCache {
* @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" );
}
@@ -566,8 +559,6 @@ class LocalisationCache {
}
}
- wfProfileOut( __METHOD__ );
-
// The JSON format only supports messages, none of the other variables, so wrap the data
return array( 'messages' => $data );
}
@@ -650,10 +641,16 @@ class LocalisationCache {
* rules, and save the compiled rules in a process-local cache.
*
* @param string $fileName
+ * @throws MWException
*/
protected function loadPluralFile( $fileName ) {
+ // Use file_get_contents instead of DOMDocument::load (T58439)
+ $xml = file_get_contents( $fileName );
+ if ( !$xml ) {
+ throw new MWException( "Unable to read plurals file $fileName" );
+ }
$doc = new DOMDocument;
- $doc->load( $fileName );
+ $doc->loadXML( $xml );
$rulesets = $doc->getElementsByTagName( "pluralRules" );
foreach ( $rulesets as $ruleset ) {
$codes = $ruleset->getAttribute( 'locales' );
@@ -687,7 +684,6 @@ class LocalisationCache {
*/
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 );
@@ -708,8 +704,6 @@ class LocalisationCache {
$deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
$deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
- wfProfileOut( __METHOD__ );
-
return $data;
}
@@ -790,17 +784,31 @@ class LocalisationCache {
}
/**
+ * Gets the combined list of messages dirs from
+ * core and extensions
+ *
+ * @since 1.25
+ * @return array
+ */
+ public function getMessagesDirs() {
+ global $wgMessagesDirs, $IP;
+ return array(
+ 'core' => "$IP/languages/i18n",
+ 'api' => "$IP/includes/api/i18n",
+ 'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
+ ) + $wgMessagesDirs;
+ }
+
+ /**
* 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 string $code
* @throws MWException
*/
public function recache( $code ) {
- global $wgExtensionMessagesFiles, $wgMessagesDirs;
- wfProfileIn( __METHOD__ );
+ global $wgExtensionMessagesFiles;
if ( !$code ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "Invalid language code requested" );
}
$this->recachedLangs[$code] = true;
@@ -843,15 +851,14 @@ class LocalisationCache {
}
$codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
-
- wfProfileIn( __METHOD__ . '-fallbacks' );
+ $messageDirs = $this->getMessagesDirs();
# 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] ) ) {
+ if ( isset( $messageDirs[$extension] ) ) {
# This extension has JSON message data; skip the PHP shim
continue;
}
@@ -879,7 +886,7 @@ class LocalisationCache {
$csData = $initialData;
# Load core messages and the extension localisations.
- foreach ( $wgMessagesDirs as $dirs ) {
+ foreach ( $messageDirs as $dirs ) {
foreach ( (array)$dirs as $dir ) {
$fileName = "$dir/$csCode.json";
$data = $this->readJSONFile( $fileName );
@@ -924,7 +931,7 @@ class LocalisationCache {
# Allow extensions an opportunity to adjust the data for this
# fallback
- wfRunHooks( 'LocalisationCacheRecacheFallback', array( $this, $csCode, &$csData ) );
+ Hooks::run( 'LocalisationCacheRecacheFallback', array( $this, $csCode, &$csData ) );
# Merge the data for this fallback into the final array
if ( $csCode === $code ) {
@@ -942,10 +949,9 @@ class LocalisationCache {
}
}
- wfProfileOut( __METHOD__ . '-fallbacks' );
-
# Add cache dependencies for any referenced globals
$deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
+ // $wgMessagesDirs is used in LocalisationCache::getMessagesDirs()
$deps['wgMessagesDirs'] = new GlobalDependency( 'wgMessagesDirs' );
$deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
@@ -981,10 +987,9 @@ class LocalisationCache {
}
# Run hooks
$purgeBlobs = true;
- wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData, &$purgeBlobs ) );
+ Hooks::run( 'LocalisationCacheRecache', array( $this, $code, &$allData, &$purgeBlobs ) );
if ( is_null( $allData['namespaceNames'] ) ) {
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
'Check that your languages/messages/MessagesEn.php file is intact.' );
}
@@ -999,7 +1004,6 @@ class LocalisationCache {
}
# Save to the persistent cache
- wfProfileIn( __METHOD__ . '-write' );
$this->store->startWrite( $code );
foreach ( $allData as $key => $value ) {
if ( in_array( $key, self::$splitKeys ) ) {
@@ -1011,16 +1015,15 @@ class LocalisationCache {
}
}
$this->store->finishWrite();
- wfProfileOut( __METHOD__ . '-write' );
# Clear out the MessageBlobStore
# HACK: If using a null (i.e. disabled) storage backend, we
# can't write to the MessageBlobStore either
if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
- MessageBlobStore::getInstance()->clear();
+ $blobStore = new MessageBlobStore();
+ $blobStore->clear();
}
- wfProfileOut( __METHOD__ );
}
/**
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index 1ef7cc58..a55e25a3 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -72,7 +72,7 @@ class MessageCache {
protected $mExpiry;
/**
- * Message cache has it's own parser which it uses to transform
+ * Message cache has its own parser which it uses to transform
* messages.
*/
protected $mParserOptions, $mParser;
@@ -266,7 +266,6 @@ class MessageCache {
}
# Loading code starts
- wfProfileIn( __METHOD__ );
$success = false; # Keep track of success
$staleCache = false; # a cache array with expired data, or false if none has been loaded
$where = array(); # Debug info, delayed to avoid spamming debug log too much
@@ -276,7 +275,6 @@ class MessageCache {
# Hash of the contents is stored in memcache, to detect if local cache goes
# out of date (e.g. due to replace() on some other server)
if ( $wgUseLocalMessageCache ) {
- wfProfileIn( __METHOD__ . '-fromlocal' );
$hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) );
if ( $hash ) {
@@ -292,7 +290,6 @@ class MessageCache {
$this->mCache[$code] = $cache;
}
}
- wfProfileOut( __METHOD__ . '-fromlocal' );
}
if ( !$success ) {
@@ -300,7 +297,6 @@ class MessageCache {
# the lock can't be acquired, wait for the other thread to finish
# and then try the global cache a second time.
for ( $failedAttempts = 0; $failedAttempts < 2; $failedAttempts++ ) {
- wfProfileIn( __METHOD__ . '-fromcache' );
$cache = $this->mMemc->get( $cacheKey );
if ( !$cache ) {
$where[] = 'global cache is empty';
@@ -314,8 +310,6 @@ class MessageCache {
$success = true;
}
- wfProfileOut( __METHOD__ . '-fromcache' );
-
if ( $success ) {
# Done, no need to retry
break;
@@ -422,8 +416,7 @@ class MessageCache {
$this->mLoadedLanguages[$code] = true;
}
$info = implode( ', ', $where );
- wfDebug( __METHOD__ . ": Loading $code... $info\n" );
- wfProfileOut( __METHOD__ );
+ wfDebugLog( 'MessageCache', __METHOD__ . ": Loading $code... $info\n" );
return $success;
}
@@ -437,7 +430,6 @@ class MessageCache {
* @return array Loaded messages for storing in caches.
*/
function loadFromDB( $code ) {
- wfProfileIn( __METHOD__ );
global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
$dbr = wfGetDB( DB_SLAVE );
$cache = array();
@@ -511,7 +503,6 @@ class MessageCache {
$cache['VERSION'] = MSG_CACHE_VERSION;
$cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
- wfProfileOut( __METHOD__ );
return $cache;
}
@@ -524,10 +515,8 @@ class MessageCache {
*/
public function replace( $title, $text ) {
global $wgMaxMsgCacheEntrySize;
- wfProfileIn( __METHOD__ );
if ( $this->mDisable ) {
- wfProfileOut( __METHOD__ );
return;
}
@@ -573,11 +562,11 @@ class MessageCache {
// Update the message in the message blob store
global $wgContLang;
- MessageBlobStore::getInstance()->updateMessage( $wgContLang->lcfirst( $msg ) );
+ $blobStore = new MessageBlobStore();
+ $blobStore->updateMessage( $wgContLang->lcfirst( $msg ) );
- wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
+ Hooks::run( 'MessageCacheReplace', array( $title, $text ) );
- wfProfileOut( __METHOD__ );
}
/**
@@ -610,7 +599,6 @@ class MessageCache {
* @return bool
*/
protected function saveToCaches( $cache, $dest, $code = false ) {
- wfProfileIn( __METHOD__ );
global $wgUseLocalMessageCache;
$cacheKey = wfMemcKey( 'messages', $code );
@@ -629,8 +617,6 @@ class MessageCache {
$this->saveToLocal( $serialized, $hash, $code );
}
- wfProfileOut( __METHOD__ );
-
return $success;
}
@@ -708,8 +694,6 @@ class MessageCache {
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
global $wgContLang;
- $section = new ProfileSection( __METHOD__ );
-
if ( is_int( $key ) ) {
// Fix numerical strings that somehow become ints
// on their way here
@@ -736,7 +720,7 @@ class MessageCache {
$lckey = $wgContLang->lcfirst( $lckey );
}
- wfRunHooks( 'MessageCache::get', array( &$lckey ) );
+ Hooks::run( 'MessageCache::get', array( &$lckey ) );
if ( ord( $lckey ) < 128 ) {
$uckey = ucfirst( $lckey );
@@ -909,7 +893,7 @@ class MessageCache {
} else {
// XXX: This is not cached in process cache, should it?
$message = false;
- wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
+ Hooks::run( 'MessagesPreLoad', array( $title, &$message ) );
if ( $message !== false ) {
return $message;
}
@@ -1056,24 +1040,22 @@ class MessageCache {
$popts->setInterfaceMessage( $interface );
$popts->setTargetLanguage( $language );
- wfProfileIn( __METHOD__ );
if ( !$title || !$title instanceof Title ) {
global $wgTitle;
+ wfDebugLog( 'GlobalTitleFail', __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.' );
$title = $wgTitle;
}
// Sometimes $wgTitle isn't set either...
if ( !$title ) {
# It's not uncommon having a null $wgTitle in scripts. See r80898
# Create a ghost title in such case
- $title = Title::newFromText( 'Dwimmerlaik' );
+ $title = Title::makeTitle( NS_SPECIAL, 'Badtitle/title not set in ' . __METHOD__ );
}
$this->mInParser = true;
$res = $parser->parse( $text, $title, $popts, $linestart );
$this->mInParser = false;
- wfProfileOut( __METHOD__ );
-
return $res;
}
diff --git a/includes/cache/ResourceFileCache.php b/includes/cache/ResourceFileCache.php
index 55da52c5..6d26a2d5 100644
--- a/includes/cache/ResourceFileCache.php
+++ b/includes/cache/ResourceFileCache.php
@@ -40,7 +40,9 @@ class ResourceFileCache extends FileCacheBase {
public static function newFromContext( ResourceLoaderContext $context ) {
$cache = new self();
- if ( $context->getOnly() === 'styles' ) {
+ if ( $context->getImage() ) {
+ $cache->mType = 'image';
+ } elseif ( $context->getOnly() === 'styles' ) {
$cache->mType = 'css';
} else {
$cache->mType = 'js';
@@ -69,7 +71,8 @@ class ResourceFileCache extends FileCacheBase {
// Get all query values
$queryVals = $context->getRequest()->getValues();
foreach ( $queryVals as $query => $val ) {
- if ( $query === 'modules' || $query === 'version' || $query === '*' ) {
+ if ( in_array( $query, array( 'modules', 'image', 'variant', 'version', '*' ) ) ) {
+ // Use file cache regardless of the value of this parameter
continue; // note: &* added as IE fix
} elseif ( $query === 'skin' && $val === $wgDefaultSkin ) {
continue;
@@ -79,6 +82,8 @@ class ResourceFileCache extends FileCacheBase {
continue;
} elseif ( $query === 'debug' && $val === 'false' ) {
continue;
+ } elseif ( $query === 'format' && $val === 'rasterized' ) {
+ continue;
}
return false;
diff --git a/includes/cache/UserCache.php b/includes/cache/UserCache.php
index 7f36f5a6..8a42489c 100644
--- a/includes/cache/UserCache.php
+++ b/includes/cache/UserCache.php
@@ -80,7 +80,6 @@ class UserCache {
* @param string $caller The calling method
*/
public function doQuery( array $userIds, $options = array(), $caller = '' ) {
- wfProfileIn( __METHOD__ );
$usersToCheck = array();
$usersToQuery = array();
@@ -134,7 +133,6 @@ class UserCache {
}
$lb->execute();
- wfProfileOut( __METHOD__ );
}
/**
diff --git a/includes/cache/bloom/BloomCache.php b/includes/cache/bloom/BloomCache.php
deleted file mode 100644
index 236db954..00000000
--- a/includes/cache/bloom/BloomCache.php
+++ /dev/null
@@ -1,323 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @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
deleted file mode 100644
index 212e5e8b..00000000
--- a/includes/cache/bloom/BloomCacheRedis.php
+++ /dev/null
@@ -1,370 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @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
deleted file mode 100644
index 9b710d79..00000000
--- a/includes/cache/bloom/BloomFilters.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @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/changes/ChangesFeed.php b/includes/changes/ChangesFeed.php
index 2d3b919d..28c2f7ed 100644
--- a/includes/changes/ChangesFeed.php
+++ b/includes/changes/ChangesFeed.php
@@ -167,14 +167,12 @@ class ChangesFeed {
* @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__ );
}
/**
@@ -183,7 +181,6 @@ class ChangesFeed {
* @return array
*/
public static function buildItems( $rows ) {
- wfProfileIn( __METHOD__ );
$items = array();
# Merge adjacent edits by one user
@@ -234,7 +231,6 @@ class ChangesFeed {
);
}
- wfProfileOut( __METHOD__ );
return $items;
}
}
diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php
index 03d1289f..932006d4 100644
--- a/includes/changes/ChangesList.php
+++ b/includes/changes/ChangesList.php
@@ -67,7 +67,7 @@ class ChangesList extends ContextSource {
$user = $context->getUser();
$sk = $context->getSkin();
$list = null;
- if ( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) {
+ if ( Hooks::run( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) {
$new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context );
@@ -180,7 +180,7 @@ class ChangesList extends ContextSource {
* @param ResultWrapper|array $rows
*/
public function initChangesListRows( $rows ) {
- wfRunHooks( 'ChangesListInitRows', array( $this, $rows ) );
+ Hooks::run( 'ChangesListInitRows', array( $this, $rows ) );
}
/**
@@ -364,7 +364,7 @@ class ChangesList extends ContextSource {
# RTL/LTR marker
$articlelink .= $this->getLanguage()->getDirMark();
- wfRunHooks( 'ChangesListInsertArticleLink',
+ Hooks::run( 'ChangesListInsertArticleLink',
array( &$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched ) );
$s .= " $articlelink";
diff --git a/includes/changes/EnhancedChangesList.php b/includes/changes/EnhancedChangesList.php
index 4ab77297..19277f10 100644
--- a/includes/changes/EnhancedChangesList.php
+++ b/includes/changes/EnhancedChangesList.php
@@ -34,6 +34,7 @@ class EnhancedChangesList extends ChangesList {
/**
* @param IContextSource|Skin $obj
+ * @throws MWException
*/
public function __construct( $obj ) {
if ( $obj instanceof Skin ) {
@@ -88,7 +89,6 @@ class EnhancedChangesList extends ChangesList {
* @return string
*/
public function recentChangesLine( &$baseRC, $watched = false ) {
- wfProfileIn( __METHOD__ );
$date = $this->getLanguage()->userDate(
$baseRC->mAttribs['rc_timestamp'],
@@ -109,8 +109,6 @@ class EnhancedChangesList extends ChangesList {
$cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $baseRC, $watched );
$this->addCacheEntry( $cacheEntry );
- wfProfileOut( __METHOD__ );
-
return $ret;
}
@@ -160,7 +158,6 @@ class EnhancedChangesList extends ChangesList {
* @return string
*/
protected function recentChangesBlockGroup( $block ) {
- wfProfileIn( __METHOD__ );
# Add the namespace and title of the block as part of the class
$classes = array( 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' );
@@ -184,14 +181,12 @@ class EnhancedChangesList extends ChangesList {
$isnew = false;
$allBots = true;
$allMinors = true;
- $curId = $currentRevision = 0;
+ $curId = 0;
# 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 ) {
$isnew = true;
}
@@ -215,9 +210,6 @@ class EnhancedChangesList extends ChangesList {
if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
$curId = $rcObj->mAttribs['rc_cur_id'];
}
- if ( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) {
- $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
- }
if ( !$rcObj->mAttribs['rc_bot'] ) {
$allBots = false;
@@ -278,87 +270,7 @@ class EnhancedChangesList extends ChangesList {
$queryParams['curid'] = $curId;
- # Changes message
- static $nchanges = array();
- static $sinceLastVisitMsg = array();
-
- $n = count( $block );
- if ( !isset( $nchanges[$n] ) ) {
- $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
- }
-
- $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 ) {
- $sinceLast++;
- $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
- }
- }
- if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
- $sinceLastVisitMsg[$sinceLast] =
- $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
- }
-
- # 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];
- } elseif ( $isnew ) {
- $logtext .= $nchanges[$n];
- } else {
- $logtext .= Linker::link(
- $block0->getTitle(),
- $nchanges[$n],
- array(),
- $queryParams + array(
- 'diff' => $currentRevision,
- 'oldid' => $oldid,
- ),
- array( 'known', 'noclasses' )
- );
- if ( $sinceLast > 0 && $sinceLast < $n ) {
- $logtext .= $this->message['pipe-separator'] . Linker::link(
- $block0->getTitle(),
- $sinceLastVisitMsg[$sinceLast],
- array(),
- $queryParams + array(
- 'diff' => $currentRevision,
- 'oldid' => $unvisitedOldid,
- ),
- array( 'known', 'noclasses' )
- );
- }
- }
- }
-
- # History
- if ( $allLogs ) {
- // don't show history link for logs
- } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
- $logtext .= $this->message['pipe-separator'] . $this->message['enhancedrc-history'];
- } else {
- $params = $queryParams;
- $params['action'] = 'history';
-
- $logtext .= $this->message['pipe-separator'] .
- Linker::linkKnown(
- $block0->getTitle(),
- $this->message['enhancedrc-history'],
- array(),
- $params
- );
- }
-
- if ( $logtext !== '' ) {
- $r .= $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
- }
+ $r .= $this->getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden );
$r .= ' <span class="mw-changeslist-separator">. .</span> ';
@@ -384,7 +296,7 @@ class EnhancedChangesList extends ChangesList {
}
$r .= $users;
- $r .= $this->numberofWatchingusers( $block0->numberofWatchingusers );
+ $r .= $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
$r .= '</td></tr>';
# Sub-entries
@@ -468,19 +380,124 @@ class EnhancedChangesList extends ChangesList {
$this->rcCacheIndex++;
- wfProfileOut( __METHOD__ );
-
return $r;
}
/**
+ * Generates amount of changes (linking to diff ) & link to history.
+ *
+ * @param array $block
+ * @param array $queryParams
+ * @param bool $allLogs
+ * @param bool $isnew
+ * @param bool $namehidden
+ * @return string
+ */
+ protected function getLogText( $block, $queryParams, $allLogs, $isnew, $namehidden ) {
+ # Changes message
+ static $nchanges = array();
+ static $sinceLastVisitMsg = array();
+
+ $n = count( $block );
+ if ( !isset( $nchanges[$n] ) ) {
+ $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped();
+ }
+
+ $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 ) {
+ $sinceLast++;
+ $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid'];
+ }
+ }
+ if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) {
+ $sinceLastVisitMsg[$sinceLast] =
+ $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped();
+ }
+
+ $currentRevision = 0;
+ foreach ( $block as $rcObj ) {
+ if ( !$currentRevision ) {
+ $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
+ }
+ }
+
+ # Total change link
+ $links = array();
+ /** @var $block0 RecentChange */
+ $block0 = $block[0];
+ $last = $block[count( $block ) - 1];
+ if ( !$allLogs ) {
+ if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $links['total-changes'] = $nchanges[$n];
+ } elseif ( $isnew ) {
+ $links['total-changes'] = $nchanges[$n];
+ } else {
+ $links['total-changes'] = Linker::link(
+ $block0->getTitle(),
+ $nchanges[$n],
+ array(),
+ $queryParams + array(
+ 'diff' => $currentRevision,
+ 'oldid' => $last->mAttribs['rc_last_oldid'],
+ ),
+ array( 'known', 'noclasses' )
+ );
+ if ( $sinceLast > 0 && $sinceLast < $n ) {
+ $links['total-changes-since-last'] = Linker::link(
+ $block0->getTitle(),
+ $sinceLastVisitMsg[$sinceLast],
+ array(),
+ $queryParams + array(
+ 'diff' => $currentRevision,
+ 'oldid' => $unvisitedOldid,
+ ),
+ array( 'known', 'noclasses' )
+ );
+ }
+ }
+ }
+
+ # History
+ if ( $allLogs ) {
+ // don't show history link for logs
+ } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
+ $links['history'] = $this->message['enhancedrc-history'];
+ } else {
+ $params = $queryParams;
+ $params['action'] = 'history';
+
+ $links['history'] = Linker::linkKnown(
+ $block0->getTitle(),
+ $this->message['enhancedrc-history'],
+ array(),
+ $params
+ );
+ }
+
+ # Allow others to alter, remove or add to these links
+ Hooks::run( 'EnhancedChangesList::getLogText',
+ array( $this, &$links, $block ) );
+
+ if ( !$links ) {
+ return '';
+ }
+
+ $logtext = implode( $this->message['pipe-separator'], $links );
+ $logtext = $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
+ return ' ' . $logtext;
+ }
+
+ /**
* Enhanced RC ungrouped line.
*
* @param RecentChange|RCCacheEntry $rcObj
* @return string A HTML formatted line (generated using $r)
*/
protected function recentChangesBlockLine( $rcObj ) {
- wfProfileIn( __METHOD__ );
$query['curid'] = $rcObj->mAttribs['rc_cur_id'];
$type = $rcObj->mAttribs['rc_type'];
@@ -552,8 +569,6 @@ class EnhancedChangesList extends ChangesList {
$r .= "</td></tr></table>\n";
- wfProfileOut( __METHOD__ );
-
return $r;
}
@@ -568,8 +583,6 @@ class EnhancedChangesList extends ChangesList {
return '';
}
- wfProfileIn( __METHOD__ );
-
$blockOut = '';
foreach ( $this->rc_cache as $block ) {
if ( count( $block ) < 2 ) {
@@ -579,8 +592,6 @@ class EnhancedChangesList extends ChangesList {
}
}
- wfProfileOut( __METHOD__ );
-
return '<div>' . $blockOut . '</div>';
}
diff --git a/includes/changes/OldChangesList.php b/includes/changes/OldChangesList.php
index 4eed9262..4ce564d1 100644
--- a/includes/changes/OldChangesList.php
+++ b/includes/changes/OldChangesList.php
@@ -32,7 +32,6 @@ class OldChangesList extends ChangesList {
* @return string|bool
*/
public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
- wfProfileIn( __METHOD__ );
$classes = array();
// use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468)
@@ -56,14 +55,10 @@ class OldChangesList extends ChangesList {
$rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
}
- if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$html, $rc, &$classes ) ) ) {
- wfProfileOut( __METHOD__ );
-
+ if ( !Hooks::run( 'OldChangesListRecentChangesLine', array( &$this, &$html, $rc, &$classes ) ) ) {
return false;
}
- wfProfileOut( __METHOD__ );
-
$dateheader = ''; // $html now contains only <li>...</li>, for hooks' convenience.
$this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
@@ -73,7 +68,7 @@ class OldChangesList extends ChangesList {
/**
* @param RecentChange $rc
* @param string[] &$classes
- * @param boolean $watched
+ * @param bool $watched
*
* @return string
*/
diff --git a/includes/changes/RecentChange.php b/includes/changes/RecentChange.php
index e33274e8..b430bab9 100644
--- a/includes/changes/RecentChange.php
+++ b/includes/changes/RecentChange.php
@@ -257,7 +257,7 @@ class RecentChange {
public function getPerformer() {
if ( $this->mPerformer === false ) {
if ( $this->mAttribs['rc_user'] ) {
- $this->mPerformer = User::newFromID( $this->mAttribs['rc_user'] );
+ $this->mPerformer = User::newFromId( $this->mAttribs['rc_user'] );
} else {
$this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false );
}
@@ -283,7 +283,7 @@ class RecentChange {
}
# If our database is strict about IP addresses, use NULL instead of an empty string
- if ( $dbw->strictIPs() and $this->mAttribs['rc_ip'] == '' ) {
+ if ( $dbw->strictIPs() && $this->mAttribs['rc_ip'] == '' ) {
unset( $this->mAttribs['rc_ip'] );
}
@@ -298,7 +298,7 @@ class RecentChange {
$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
- if ( $dbw->cascadingDeletes() and $this->mAttribs['rc_cur_id'] == 0 ) {
+ if ( $dbw->cascadingDeletes() && $this->mAttribs['rc_cur_id'] == 0 ) {
unset( $this->mAttribs['rc_cur_id'] );
}
@@ -309,7 +309,7 @@ class RecentChange {
$this->mAttribs['rc_id'] = $dbw->insertId();
# Notify extensions
- wfRunHooks( 'RecentChange_save', array( &$this ) );
+ Hooks::run( 'RecentChange_save', array( &$this ) );
# Notify external application via UDP
if ( !$noudp ) {
@@ -321,7 +321,7 @@ class RecentChange {
$editor = $this->getPerformer();
$title = $this->getTitle();
- if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title, $this ) ) ) {
+ if ( Hooks::run( 'AbortEmailNotification', array( $editor, $title, $this ) ) ) {
# @todo FIXME: This would be better as an extension hook
$enotif = new EmailNotification();
$enotif->notifyOnPageChange( $editor, $title,
@@ -445,12 +445,12 @@ class RecentChange {
// Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
$right = $auto ? 'autopatrol' : 'patrol';
$errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) );
- if ( !wfRunHooks( 'MarkPatrolled', array( $this->getAttribute( 'rc_id' ), &$user, false ) ) ) {
+ if ( !Hooks::run( 'MarkPatrolled', array( $this->getAttribute( 'rc_id' ), &$user, false ) ) ) {
$errors[] = array( 'hookaborted' );
}
// Users without the 'autopatrol' right can't patrol their
// own revisions
- if ( $user->getName() == $this->getAttribute( 'rc_user_text' )
+ if ( $user->getName() === $this->getAttribute( 'rc_user_text' )
&& !$user->isAllowed( 'autopatrol' )
) {
$errors[] = array( 'markedaspatrollederror-noautopatrol' );
@@ -466,7 +466,7 @@ class RecentChange {
$this->reallyMarkPatrolled();
// Log this patrol event
PatrolLog::record( $this, $auto, $user );
- wfRunHooks( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) );
+ Hooks::run( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) );
return array();
}
@@ -796,29 +796,6 @@ class RecentChange {
return ChangesList::showCharacterDifference( $old, $new );
}
- /**
- * Purge expired changes from the recentchanges table
- * @since 1.22
- */
- public static function purgeExpiredChanges() {
- if ( wfReadOnly() ) {
- return;
- }
-
- $method = __METHOD__;
- $dbw = wfGetDB( DB_MASTER );
- $dbw->onTransactionIdle( function () use ( $dbw, $method ) {
- global $wgRCMaxAge;
-
- $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
- $dbw->delete(
- 'recentchanges',
- array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ),
- $method
- );
- } );
- }
-
private static function checkIPAddress( $ip ) {
global $wgRequest;
if ( $ip ) {
diff --git a/includes/changetags/ChangeTagsList.php b/includes/changetags/ChangeTagsList.php
new file mode 100644
index 00000000..dd8bab98
--- /dev/null
+++ b/includes/changetags/ChangeTagsList.php
@@ -0,0 +1,77 @@
+<?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 Change tagging
+ */
+
+/**
+ * Generic list for change tagging.
+ */
+abstract class ChangeTagsList extends RevisionListBase {
+ function __construct( IContextSource $context, Title $title, array $ids ) {
+ parent::__construct( $context, $title );
+ $this->ids = $ids;
+ }
+
+ /**
+ * Creates a ChangeTags*List of the requested type.
+ *
+ * @param string $typeName 'revision' or 'logentry'
+ * @param IContextSource $context
+ * @param Title $title
+ * @param array $ids
+ * @return ChangeTagsList An instance of the requested subclass
+ * @throws Exception If you give an unknown $typeName
+ */
+ public static function factory( $typeName, IContextSource $context,
+ Title $title, array $ids ) {
+
+ switch ( $typeName ) {
+ case 'revision':
+ $className = 'ChangeTagsRevisionList';
+ break;
+ case 'logentry':
+ $className = 'ChangeTagsLogList';
+ break;
+ default:
+ throw new Exception( "Class $className requested, but does not exist" );
+ }
+ return new $className( $context, $title, $ids );
+ }
+
+ /**
+ * Reload the list data from the master DB.
+ */
+ function reloadFromMaster() {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->res = $this->doQuery( $dbw );
+ }
+
+ /**
+ * Add/remove change tags from all the items in the list.
+ *
+ * @param array $tagsToAdd
+ * @param array $tagsToRemove
+ * @param array $params
+ * @param string $reason
+ * @param User $user
+ * @return Status
+ */
+ abstract function updateChangeTagsOnAll( $tagsToAdd, $tagsToRemove, $params,
+ $reason, $user );
+}
diff --git a/includes/changetags/ChangeTagsLogItem.php b/includes/changetags/ChangeTagsLogItem.php
new file mode 100644
index 00000000..565d159a
--- /dev/null
+++ b/includes/changetags/ChangeTagsLogItem.php
@@ -0,0 +1,100 @@
+<?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 Change tagging
+ */
+
+/**
+ * Item class for a logging table row with its associated change tags.
+ * @todo Abstract out a base class for this and RevDelLogItem, similar to the
+ * RevisionItem class but specifically for log items.
+ * @since 1.25
+ */
+class ChangeTagsLogItem extends RevisionItemBase {
+ 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
+ }
+
+ /**
+ * @return string Comma-separated list of tags
+ */
+ public function getTags() {
+ return $this->row->ts_tags;
+ }
+
+ /**
+ * @return string A HTML <li> element representing this revision, showing
+ * change tags and everything
+ */
+ 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() .
+ $formatter->getComment();
+
+ if ( LogEventsList::isDeleted( $this->row, LogPage::DELETED_COMMENT ) ) {
+ $comment = '<span class="history-deleted">' . $comment . '</span>';
+ }
+
+ $content = "$loglink $date $action $comment";
+ $attribs = array();
+ $tags = $this->getTags();
+ if ( $tags ) {
+ list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( $tags, 'edittags' );
+ $content .= " $tagSummary";
+ $attribs['class'] = implode( ' ', $classes );
+ }
+ return Xml::tags( 'li', $attribs, $content );
+ }
+}
diff --git a/includes/changetags/ChangeTagsLogList.php b/includes/changetags/ChangeTagsLogList.php
new file mode 100644
index 00000000..fe80695f
--- /dev/null
+++ b/includes/changetags/ChangeTagsLogList.php
@@ -0,0 +1,89 @@
+<?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 Change tagging
+ */
+
+/**
+ * Stores a list of taggable log entries.
+ * @since 1.25
+ */
+class ChangeTagsLogList extends ChangeTagsList {
+ public function getType() {
+ return 'logentry';
+ }
+
+ /**
+ * @param DatabaseBase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ $queryInfo = DatabaseLogEntry::getSelectQueryData();
+ $queryInfo['conds'] += array( 'log_id' => $ids );
+ $queryInfo['options'] += array( 'ORDER BY' => 'log_id DESC' );
+ ChangeTags::modifyDisplayQuery(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ $queryInfo['join_conds'],
+ $queryInfo['options']
+ );
+ return $db->select(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ __METHOD__,
+ $queryInfo['options'],
+ $queryInfo['join_conds']
+ );
+ }
+
+ public function newItem( $row ) {
+ return new ChangeTagsLogItem( $this, $row );
+ }
+
+ /**
+ * Add/remove change tags from all the log entries in the list.
+ *
+ * @param array $tagsToAdd
+ * @param array $tagsToRemove
+ * @param array $params
+ * @param string $reason
+ * @param User $user
+ * @return Status
+ */
+ public function updateChangeTagsOnAll( $tagsToAdd, $tagsToRemove, $params,
+ $reason, $user ) {
+
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
+ for ( $this->reset(); $this->current(); $this->next() ) {
+ // @codingStandardsIgnoreEnd
+ $item = $this->current();
+ $status = ChangeTags::updateTagsWithChecks( $tagsToAdd, $tagsToRemove,
+ null, null, $item->getId(), $params, $reason, $user );
+ // Should only fail on second and subsequent times if the user trips
+ // the rate limiter
+ if ( !$status->isOK() ) {
+ break;
+ }
+ }
+
+ return $status;
+ }
+}
diff --git a/includes/changetags/ChangeTagsRevisionItem.php b/includes/changetags/ChangeTagsRevisionItem.php
new file mode 100644
index 00000000..e90a1b4f
--- /dev/null
+++ b/includes/changetags/ChangeTagsRevisionItem.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
+ * @ingroup Change tagging
+ */
+
+/**
+ * Item class for a live revision table row with its associated change tags.
+ * @since 1.25
+ */
+class ChangeTagsRevisionItem extends RevisionItem {
+ /**
+ * @return string Comma-separated list of tags
+ */
+ public function getTags() {
+ return $this->row->ts_tags;
+ }
+
+ /**
+ * @return string A HTML <li> element representing this revision, showing
+ * change tags and everything
+ */
+ 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>";
+ }
+
+ $content = "$difflink $revlink $userlink $comment";
+ $attribs = array();
+ $tags = $this->getTags();
+ if ( $tags ) {
+ list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( $tags, 'edittags' );
+ $content .= " $tagSummary";
+ $attribs['class'] = implode( ' ', $classes );
+ }
+ return Xml::tags( 'li', $attribs, $content );
+ }
+}
diff --git a/includes/changetags/ChangeTagsRevisionList.php b/includes/changetags/ChangeTagsRevisionList.php
new file mode 100644
index 00000000..842d3272
--- /dev/null
+++ b/includes/changetags/ChangeTagsRevisionList.php
@@ -0,0 +1,99 @@
+<?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 Change tagging
+ */
+
+/**
+ * Stores a list of taggable revisions.
+ * @since 1.25
+ */
+class ChangeTagsRevisionList extends ChangeTagsList {
+ public function getType() {
+ return 'revision';
+ }
+
+ /**
+ * @param DatabaseBase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ $queryInfo = array(
+ 'tables' => array( 'revision', 'user' ),
+ 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
+ 'conds' => array(
+ 'rev_page' => $this->title->getArticleID(),
+ 'rev_id' => $ids,
+ ),
+ 'options' => array( 'ORDER BY' => 'rev_id DESC' ),
+ 'join_conds' => array(
+ 'page' => Revision::pageJoinCond(),
+ 'user' => Revision::userJoinCond(),
+ ),
+ );
+ ChangeTags::modifyDisplayQuery(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ $queryInfo['join_conds'],
+ $queryInfo['options']
+ );
+ return $db->select(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ __METHOD__,
+ $queryInfo['options'],
+ $queryInfo['join_conds']
+ );
+ }
+
+ public function newItem( $row ) {
+ return new ChangeTagsRevisionItem( $this, $row );
+ }
+
+ /**
+ * Add/remove change tags from all the revisions in the list.
+ *
+ * @param array $tagsToAdd
+ * @param array $tagsToRemove
+ * @param array $params
+ * @param string $reason
+ * @param User $user
+ * @return Status
+ */
+ public function updateChangeTagsOnAll( $tagsToAdd, $tagsToRemove, $params,
+ $reason, $user ) {
+
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
+ for ( $this->reset(); $this->current(); $this->next() ) {
+ // @codingStandardsIgnoreEnd
+ $item = $this->current();
+ $status = ChangeTags::updateTagsWithChecks( $tagsToAdd, $tagsToRemove,
+ null, $item->getId(), null, $params, $reason, $user );
+ // Should only fail on second and subsequent times if the user trips
+ // the rate limiter
+ if ( !$status->isOK() ) {
+ break;
+ }
+ }
+
+ return $status;
+ }
+}
diff --git a/includes/config/ConfigException.php b/includes/config/ConfigException.php
index 75cd5eeb..3b3ba9de 100644
--- a/includes/config/ConfigException.php
+++ b/includes/config/ConfigException.php
@@ -25,5 +25,5 @@
*
* @since 1.23
*/
-class ConfigException extends MWException {
+class ConfigException extends Exception {
}
diff --git a/includes/config/GlobalVarConfig.php b/includes/config/GlobalVarConfig.php
index 39d6e8e1..589f7d35 100644
--- a/includes/config/GlobalVarConfig.php
+++ b/includes/config/GlobalVarConfig.php
@@ -63,15 +63,6 @@ class GlobalVarConfig implements Config {
}
/**
- * @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.
@@ -93,16 +84,4 @@ class GlobalVarConfig implements Config {
$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/content/AbstractContent.php b/includes/content/AbstractContent.php
index 9d257a6a..6542d7df 100644
--- a/includes/content/AbstractContent.php
+++ b/includes/content/AbstractContent.php
@@ -204,13 +204,13 @@ abstract class AbstractContent implements Content {
* Returns a list of DataUpdate objects for recording information about this
* Content in some secondary data store.
*
- * This default implementation calls
- * $this->getParserOutput( $content, $title, null, null, false ),
- * and then calls getSecondaryDataUpdates( $title, $recursive ) on the
- * resulting ParserOutput object.
+ * This default implementation returns a LinksUpdate object and calls the
+ * SecondaryDataUpdates hook.
*
* Subclasses may override this to determine the secondary data updates more
* efficiently, preferably without the need to generate a parser output object.
+ * They should however make sure to call SecondaryDataUpdates to give extensions
+ * a chance to inject additional updates.
*
* @since 1.21
*
@@ -224,12 +224,19 @@ abstract class AbstractContent implements Content {
* @see Content::getSecondaryDataUpdates()
*/
public function getSecondaryDataUpdates( Title $title, Content $old = null,
- $recursive = true, ParserOutput $parserOutput = null ) {
+ $recursive = true, ParserOutput $parserOutput = null
+ ) {
if ( $parserOutput === null ) {
$parserOutput = $this->getParserOutput( $title, null, null, false );
}
- return $parserOutput->getSecondaryDataUpdates( $title, $recursive );
+ $updates = array(
+ new LinksUpdate( $title, $parserOutput, $recursive )
+ );
+
+ Hooks::run( 'SecondaryDataUpdates', array( $title, $old, $recursive, $parserOutput, &$updates ) );
+
+ return $updates;
}
/**
@@ -386,7 +393,7 @@ abstract class AbstractContent implements Content {
*
* @see Content::prepareSave
*/
- public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) {
+ public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user ) {
if ( $this->isValid() ) {
return Status::newGood();
} else {
@@ -446,7 +453,7 @@ abstract class AbstractContent implements Content {
$lossy = ( $lossy === 'lossy' ); // string flag, convert to boolean for convenience
$result = false;
- wfRunHooks( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) );
+ Hooks::run( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) );
return $result;
}
@@ -480,7 +487,7 @@ abstract class AbstractContent implements Content {
$po = new ParserOutput();
- if ( wfRunHooks( 'ContentGetParserOutput',
+ if ( Hooks::run( 'ContentGetParserOutput',
array( $this, $title, $revId, $options, $generateHtml, &$po ) ) ) {
// Save and restore the old value, just in case something is reusing
@@ -491,6 +498,8 @@ abstract class AbstractContent implements Content {
$options->setRedirectTarget( $oldRedir );
}
+ Hooks::run( 'ContentAlterParserOutput', array( $this, $title, $po ) );
+
return $po;
}
diff --git a/includes/content/CodeContentHandler.php b/includes/content/CodeContentHandler.php
index 447a2a73..694b633e 100644
--- a/includes/content/CodeContentHandler.php
+++ b/includes/content/CodeContentHandler.php
@@ -58,6 +58,7 @@ abstract class CodeContentHandler extends TextContentHandler {
/**
* @return string
+ * @throws MWException
*/
protected function getContentClass() {
throw new MWException( 'Subclass must override' );
diff --git a/includes/content/Content.php b/includes/content/Content.php
index 61b9254f..8ed761f5 100644
--- a/includes/content/Content.php
+++ b/includes/content/Content.php
@@ -292,6 +292,9 @@ interface Content {
* Subclasses may implement this to determine the necessary updates more
* efficiently, or make use of information about the old content.
*
+ * @note Implementations should call the SecondaryDataUpdates hook, like
+ * AbstractContent does.
+ *
* @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
@@ -461,7 +464,7 @@ interface Content {
*
* @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 int $parentRevId The ID of the current revision
* @param User $user
*
* @return Status A status object indicating whether the content was
@@ -470,7 +473,7 @@ interface Content {
*
* @see WikiPage::doEditContent()
*/
- public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user );
+ public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user );
/**
* Returns a list of updates to perform when this content is deleted.
diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php
index ac417223..371b267e 100644
--- a/includes/content/ContentHandler.php
+++ b/includes/content/ContentHandler.php
@@ -201,7 +201,7 @@ abstract class ContentHandler {
$model = MWNamespace::getNamespaceContentModel( $ns );
// Hook can determine default model
- if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) {
+ if ( !Hooks::run( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) {
if ( !is_null( $model ) ) {
return $model;
}
@@ -214,7 +214,7 @@ abstract class ContentHandler {
}
// Hook can force JS/CSS
- wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) );
+ Hooks::run( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ), '1.25' );
// Is this a .css subpage of a user page?
$isJsCssSubpage = NS_USER == $ns
@@ -229,7 +229,7 @@ abstract class ContentHandler {
$isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage;
// Hook can override $isWikitext
- wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) );
+ Hooks::run( 'TitleIsWikitextPage', array( $title, &$isWikitext ), '1.25' );
if ( !$isWikitext ) {
switch ( $ext ) {
@@ -318,7 +318,7 @@ abstract class ContentHandler {
if ( empty( $wgContentHandlers[$modelId] ) ) {
$handler = null;
- wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
+ Hooks::run( 'ContentHandlerForModelID', array( $modelId, &$handler ) );
if ( $handler === null ) {
throw new MWException( "No handler for model '$modelId' registered in \$wgContentHandlers" );
@@ -626,8 +626,15 @@ abstract class ContentHandler {
public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0,
$rcid = 0, //FIXME: Deprecated, no longer used
$refreshCache = false, $unhide = false ) {
- $diffEngineClass = $this->getDiffEngineClass();
+ // hook: get difference engine
+ $differenceEngine = null;
+ if ( !wfRunHooks( 'GetDifferenceEngine',
+ array( $context, $old, $new, $refreshCache, $unhide, &$differenceEngine )
+ ) ) {
+ return $differenceEngine;
+ }
+ $diffEngineClass = $this->getDiffEngineClass();
return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
}
@@ -660,7 +667,7 @@ abstract class ContentHandler {
$pageLang = wfGetLangObj( $lang );
}
- wfRunHooks( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) );
+ Hooks::run( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) );
return wfGetLangObj( $pageLang );
}
@@ -719,7 +726,7 @@ abstract class ContentHandler {
public function canBeUsedOn( Title $title ) {
$ok = true;
- wfRunHooks( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) );
+ Hooks::run( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) );
return $ok;
}
@@ -1151,7 +1158,7 @@ abstract class ContentHandler {
}
// call the hook functions
- $ok = wfRunHooks( $event, $args );
+ $ok = Hooks::run( $event, $args );
// see if the hook changed the text
foreach ( $contentTexts as $k => $orig ) {
diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php
index 457b83d7..d2218971 100644
--- a/includes/content/JavaScriptContentHandler.php
+++ b/includes/content/JavaScriptContentHandler.php
@@ -1,7 +1,5 @@
<?php
/**
- * Content handler for JavaScript 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
@@ -23,9 +21,10 @@
/**
* Content handler for JavaScript pages.
*
+ * @todo Create a ScriptContentHandler base class, do highlighting stuff there?
+ *
* @since 1.21
* @ingroup Content
- * @todo make ScriptContentHandler base class, do highlighting stuff there?
*/
class JavaScriptContentHandler extends CodeContentHandler {
@@ -36,6 +35,9 @@ class JavaScriptContentHandler extends CodeContentHandler {
parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) );
}
+ /**
+ * @return string
+ */
protected function getContentClass() {
return 'JavaScriptContent';
}
diff --git a/includes/content/JsonContent.php b/includes/content/JsonContent.php
index b36827c5..bf3a25bf 100644
--- a/includes/content/JsonContent.php
+++ b/includes/content/JsonContent.php
@@ -14,53 +14,83 @@
*/
class JsonContent extends TextContent {
+ /**
+ * @since 1.25
+ * @var Status
+ */
+ protected $jsonParse;
+
+ /**
+ * @param string $text JSON
+ */
public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) {
parent::__construct( $text, $modelId );
}
/**
* Decodes the JSON into a PHP associative array.
- * @return array
+ *
+ * @deprecated since 1.25 Use getData instead.
+ * @return array|null
*/
public function getJsonData() {
+ wfDeprecated( __METHOD__, '1.25' );
return FormatJson::decode( $this->getNativeData(), true );
}
/**
- * @return bool Whether content is valid JSON.
+ * Decodes the JSON string.
+ *
+ * Note that this parses it without casting objects to associative arrays.
+ * Objects and arrays are kept as distinguishable types in the PHP values.
+ *
+ * @return Status
+ */
+ public function getData() {
+ if ( $this->jsonParse === null ) {
+ $this->jsonParse = FormatJson::parse( $this->getNativeData() );
+ }
+ return $this->jsonParse;
+ }
+
+ /**
+ * @return bool Whether content is valid.
*/
public function isValid() {
- return $this->getJsonData() !== null;
+ return $this->getData()->isGood();
}
/**
- * Pretty-print JSON
+ * Pretty-print JSON.
*
- * @return bool|null|string
+ * If called before validation, it may return JSON "null".
+ *
+ * @return string
*/
public function beautifyJSON() {
- $decoded = FormatJson::decode( $this->getNativeData(), true );
- if ( !is_array( $decoded ) ) {
- return null;
- }
- return FormatJson::encode( $decoded, true );
-
+ return FormatJson::encode( $this->getData()->getValue(), true, FormatJson::UTF8_OK );
}
/**
* 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 ) {
+ // FIXME: WikiPage::doEditContent invokes PST before validation. As such, native data
+ // may be invalid (though PST result is discarded later in that case).
+ if ( !$this->isValid() ) {
+ return $this;
+ }
+
return new static( $this->beautifyJSON() );
}
/**
- * Set the HTML and add the appropriate styles
- *
+ * Set the HTML and add the appropriate styles.
*
* @param Title $title
* @param int $revId
@@ -71,50 +101,150 @@ class JsonContent extends TextContent {
protected function fillParserOutput( Title $title, $revId,
ParserOptions $options, $generateHtml, ParserOutput &$output
) {
- if ( $generateHtml ) {
- $output->setText( $this->objectTable( $this->getJsonData() ) );
+ // FIXME: WikiPage::doEditContent generates parser output before validation.
+ // As such, native data may be invalid (though output is discarded later in that case).
+ if ( $generateHtml && $this->isValid() ) {
+ $output->setText( $this->rootValueTable( $this->getData()->getValue() ) );
$output->addModuleStyles( 'mediawiki.content.json' );
} else {
$output->setText( '' );
}
}
+
/**
- * Constructs an HTML representation of a JSON object.
- * @param array $mapping
+ * Construct HTML table representation of any JSON value.
+ *
+ * See also valueCell, which is similar.
+ *
+ * @param mixed $val
+ * @return string HTML.
+ */
+ protected function rootValueTable( $val ) {
+ if ( is_object( $val ) ) {
+ return self::objectTable( $val );
+ }
+
+ if ( is_array( $val ) ) {
+ // Wrap arrays in another array so that they're visually boxed in a container.
+ // Otherwise they are visually indistinguishable from a single value.
+ return self::arrayTable( array( $val ) );
+ }
+
+ return Html::rawElement( 'table', array( 'class' => 'mw-json mw-json-single-value' ),
+ Html::rawElement( 'tbody', array(),
+ Html::rawElement( 'tr', array(),
+ Html::element( 'td', array(), self::primitiveValue( $val ) )
+ )
+ )
+ );
+ }
+
+ /**
+ * Create HTML table representing a JSON object.
+ *
+ * @param stdClass $mapping
* @return string HTML
*/
protected function objectTable( $mapping ) {
$rows = array();
+ $empty = true;
foreach ( $mapping as $key => $val ) {
$rows[] = $this->objectRow( $key, $val );
+ $empty = false;
+ }
+ if ( $empty ) {
+ $rows[] = Html::rawElement( 'tr', array(),
+ Html::element( 'td', array( 'class' => 'mw-json-empty' ),
+ wfMessage( 'content-json-empty-object' )->text()
+ )
+ );
}
- return Xml::tags( 'table', array( 'class' => 'mw-json' ),
- Xml::tags( 'tbody', array(), join( "\n", $rows ) )
+ return Html::rawElement( 'table', array( 'class' => 'mw-json' ),
+ Html::rawElement( 'tbody', array(), join( '', $rows ) )
);
}
/**
- * Constructs HTML representation of a single key-value pair.
+ * Create HTML table row representing one object property.
+ *
* @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 );
- }
+ $th = Html::element( 'th', array(), $key );
+ $td = self::valueCell( $val );
+ return Html::rawElement( 'tr', array(), $th . $td );
+ }
- $td = Xml::elementClean( 'td', array( 'class' => 'value' ), $val );
+ /**
+ * Create HTML table representing a JSON array.
+ *
+ * @param array $mapping
+ * @return string HTML
+ */
+ protected function arrayTable( $mapping ) {
+ $rows = array();
+ $empty = true;
+
+ foreach ( $mapping as $val ) {
+ $rows[] = $this->arrayRow( $val );
+ $empty = false;
+ }
+ if ( $empty ) {
+ $rows[] = Html::rawElement( 'tr', array(),
+ Html::element( 'td', array( 'class' => 'mw-json-empty' ),
+ wfMessage( 'content-json-empty-array' )->text()
+ )
+ );
}
+ return Html::rawElement( 'table', array( 'class' => 'mw-json' ),
+ Html::rawElement( 'tbody', array(), join( "\n", $rows ) )
+ );
+ }
- return Xml::tags( 'tr', array(), $th . $td );
+ /**
+ * Create HTML table row representing the value in an array.
+ *
+ * @param mixed $val
+ * @return string HTML.
+ */
+ protected function arrayRow( $val ) {
+ $td = self::valueCell( $val );
+ return Html::rawElement( 'tr', array(), $td );
}
+ /**
+ * Construct HTML table cell representing any JSON value.
+ *
+ * @param mixed $val
+ * @return string HTML.
+ */
+ protected function valueCell( $val ) {
+ if ( is_object( $val ) ) {
+ return Html::rawElement( 'td', array(), self::objectTable( $val ) );
+ }
+
+ if ( is_array( $val ) ) {
+ return Html::rawElement( 'td', array(), self::arrayTable( $val ) );
+ }
+
+ return Html::element( 'td', array( 'class' => 'value' ), self::primitiveValue( $val ) );
+ }
+
+ /**
+ * Construct text representing a JSON primitive value.
+ *
+ * @param mixed $val
+ * @return string Text.
+ */
+ protected function primitiveValue( $val ) {
+ if ( is_string( $val ) ) {
+ // Don't FormatJson::encode for strings since we want quotes
+ // and new lines to render visually instead of escaped.
+ return '"' . $val . '"';
+ }
+ return FormatJson::encode( $val );
+ }
}
diff --git a/includes/content/JsonContentHandler.php b/includes/content/JsonContentHandler.php
index 392ce37b..b149f528 100644
--- a/includes/content/JsonContentHandler.php
+++ b/includes/content/JsonContentHandler.php
@@ -1,15 +1,31 @@
<?php
/**
- * JSON Schema Content 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.
*
- * @file
+ * 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.
*
- * @author Ori Livneh <ori@wikimedia.org>
- * @author Kunal Mehta <legoktm@gmail.com>
+ * 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
*/
/**
+ * Content handler for JSON.
+ *
+ * @author Ori Livneh <ori@wikimedia.org>
+ * @author Kunal Mehta <legoktm@gmail.com>
+ *
* @since 1.24
+ * @ingroup Content
*/
class JsonContentHandler extends CodeContentHandler {
diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php
index c479f202..baea8125 100644
--- a/includes/content/TextContent.php
+++ b/includes/content/TextContent.php
@@ -37,6 +37,7 @@ class TextContent extends AbstractContent {
/**
* @param string $text
* @param string $model_id
+ * @throws MWException
*/
public function __construct( $text, $model_id = CONTENT_MODEL_TEXT ) {
parent::__construct( $model_id );
diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php
index 9a8ab3a6..dbe09f91 100644
--- a/includes/content/WikitextContent.php
+++ b/includes/content/WikitextContent.php
@@ -68,13 +68,11 @@ class WikitextContent extends TextContent {
* @see Content::replaceSection()
*/
public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
- wfProfileIn( __METHOD__ );
$myModelId = $this->getModel();
$sectionModelId = $with->getModel();
if ( $sectionModelId != $myModelId ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "Incompatible content model for section: " .
"document uses $myModelId but " .
"section uses $sectionModelId." );
@@ -84,7 +82,6 @@ class WikitextContent extends TextContent {
$text = $with->getNativeData();
if ( strval( $sectionId ) === '' ) {
- wfProfileOut( __METHOD__ );
return $with; # XXX: copy first?
}
@@ -93,7 +90,7 @@ class WikitextContent extends TextContent {
# Inserting a new section
$subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
- if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
+ if ( Hooks::run( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
$text = strlen( trim( $oldtext ) ) > 0
? "{$oldtext}\n\n{$subject}{$text}"
: "{$subject}{$text}";
@@ -107,8 +104,6 @@ class WikitextContent extends TextContent {
$newContent = new static( $text );
- wfProfileOut( __METHOD__ );
-
return $newContent;
}
diff --git a/includes/context/ContextSource.php b/includes/context/ContextSource.php
index 076504ec..d526d84b 100644
--- a/includes/context/ContextSource.php
+++ b/includes/context/ContextSource.php
@@ -1,7 +1,5 @@
<?php
/**
- * Request-dependant objects containers.
- *
* This 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
@@ -17,8 +15,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @since 1.18
- *
* @author Happy-melon
* @file
*/
@@ -26,6 +22,8 @@
/**
* The simplest way of implementing IContextSource is to hold a RequestContext as a
* member variable and provide accessors to it.
+ *
+ * @since 1.18
*/
abstract class ContextSource implements IContextSource {
/**
@@ -155,6 +153,17 @@ abstract class ContextSource implements IContextSource {
}
/**
+ * Get the Stats object
+ *
+ * @since 1.25
+ * @return BufferingStatsdDataFactory
+ */
+ public function getStats() {
+ return $this->getContext()->getStats();
+ }
+
+
+ /**
* Get a Message object with context set
* Parameters are the same as wfMessage()
*
diff --git a/includes/context/DerivativeContext.php b/includes/context/DerivativeContext.php
index b8966f0c..00323cae 100644
--- a/includes/context/DerivativeContext.php
+++ b/includes/context/DerivativeContext.php
@@ -1,7 +1,5 @@
<?php
/**
- * Request-dependant objects containers.
- *
* This 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
@@ -17,8 +15,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @since 1.19
- *
* @author Daniel Friesen
* @file
*/
@@ -28,6 +24,7 @@
* but allow individual pieces of context to be changed locally
* eg: A ContextSource that can inherit from the main RequestContext but have
* a different Title instance set on it.
+ * @since 1.19
*/
class DerivativeContext extends ContextSource {
/**
@@ -101,6 +98,19 @@ class DerivativeContext extends ContextSource {
}
/**
+ * Get the stats object
+ *
+ * @return BufferingStatsdDataFactory
+ */
+ public function getStats() {
+ if ( !is_null( $this->stats ) ) {
+ return $this->stats;
+ } else {
+ return $this->getContext()->getStats();
+ }
+ }
+
+ /**
* Set the WebRequest object
*
* @param WebRequest $r
diff --git a/includes/context/IContextSource.php b/includes/context/IContextSource.php
index f718103d..713c5cbf 100644
--- a/includes/context/IContextSource.php
+++ b/includes/context/IContextSource.php
@@ -1,7 +1,5 @@
<?php
/**
- * Request-dependant objects containers.
- *
* This 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,7 +22,33 @@
*/
/**
- * Interface for objects which can provide a context on request.
+ * Interface for objects which can provide a MediaWiki context on request
+ *
+ * Context objects contain request-dependent objects that manage the core
+ * web request/response logic for essentially all requests to MediaWiki.
+ * The contained objects include:
+ * a) Key objects that depend (for construction/loading) on the HTTP request
+ * b) Key objects used for response building and PHP session state control
+ * c) Performance metric deltas accumulated from request execution
+ * d) The site configuration object
+ * All of the objects are useful for the vast majority of MediaWiki requests.
+ * The site configuration object is included on grounds of extreme
+ * utility, even though it should not actually depend on the web request.
+ *
+ * More specifically, the scope of the context includes:
+ * a) Objects that represent the HTTP request/response and PHP session state
+ * b) Object representing the MediaWiki user (as determined by the HTTP request)
+ * c) Primary MediaWiki output builder objects (OutputPage, user skin object)
+ * d) The language object for the user/request
+ * e) The title and wiki page objects requested via URL (if any)
+ * f) Performance metric deltas accumulated from request execution
+ * g) The site configuration object
+ *
+ * This class is not intended as a service-locator nor a service singleton.
+ * Objects that only depend on site configuration do not belong here (aside
+ * from Config itself). Objects that represent persistent data stores do not
+ * belong here either. Session state changes should only be propagated on
+ * shutdown by separate persistence handler objects, for example.
*/
interface IContextSource {
/**
@@ -100,6 +124,14 @@ interface IContextSource {
public function getConfig();
/**
+ * Get the stats object
+ *
+ * @since 1.25
+ * @return BufferingStatsdDataFactory
+ */
+ public function getStats();
+
+ /**
* Get a Message object with context set
*
* @return Message
diff --git a/includes/context/RequestContext.php b/includes/context/RequestContext.php
index ede10fe9..4e790c04 100644
--- a/includes/context/RequestContext.php
+++ b/includes/context/RequestContext.php
@@ -1,7 +1,5 @@
<?php
/**
- * Request-dependant objects containers.
- *
* This 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
@@ -64,6 +62,11 @@ class RequestContext implements IContextSource {
private $skin;
/**
+ * @var StatsdDataFactory
+ */
+ private $stats;
+
+ /**
* @var Config
*/
private $config;
@@ -121,6 +124,22 @@ class RequestContext implements IContextSource {
}
/**
+ * Get the Stats object
+ *
+ * @return BufferingStatsdDataFactory
+ */
+ public function getStats() {
+ if ( $this->stats === null ) {
+ $config = $this->getConfig();
+ $prefix = $config->has( 'StatsdMetricPrefix' )
+ ? rtrim( $config->get( 'StatsdMetricPrefix' ), '.' )
+ : 'MediaWiki';
+ $this->stats = new BufferingStatsdDataFactory( $prefix );
+ }
+ return $this->stats;
+ }
+
+ /**
* Set the Title object
*
* @param Title $title
@@ -140,12 +159,23 @@ class RequestContext implements IContextSource {
if ( $this->title === null ) {
global $wgTitle; # fallback to $wg till we can improve this
$this->title = $wgTitle;
+ wfDebugLog( 'GlobalTitleFail', __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.' );
}
return $this->title;
}
/**
+ * Check, if a Title object is set
+ *
+ * @since 1.25
+ * @return bool
+ */
+ public function hasTitle() {
+ return $this->title !== null;
+ }
+
+ /**
* Check whether a WikiPage object can be get with getWikiPage().
* Callers should expect that an exception is thrown from getWikiPage()
* if this method returns false.
@@ -171,9 +201,8 @@ class RequestContext implements IContextSource {
* @param WikiPage $p
*/
public function setWikiPage( WikiPage $p ) {
- $contextTitle = $this->getTitle();
$pageTitle = $p->getTitle();
- if ( !$contextTitle || !$pageTitle->equals( $contextTitle ) ) {
+ if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) {
$this->setTitle( $pageTitle );
}
// Defer this to the end since setTitle sets it to null.
@@ -287,8 +316,8 @@ class RequestContext implements IContextSource {
/**
* Get the Language object.
* Initialization of user or request objects can depend on this.
- *
* @return Language
+ * @throws Exception
* @since 1.19
*/
public function getLanguage() {
@@ -308,10 +337,13 @@ class RequestContext implements IContextSource {
$request = $this->getRequest();
$user = $this->getUser();
- $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
+ $code = $request->getVal( 'uselang', 'user' );
+ if ( $code === 'user' ) {
+ $code = $user->getOption( 'language' );
+ }
$code = self::sanitizeLangCode( $code );
- wfRunHooks( 'UserGetLanguageObject', array( $user, &$code, $this ) );
+ Hooks::run( 'UserGetLanguageObject', array( $user, &$code, $this ) );
if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) {
$this->lang = $wgContLang;
@@ -348,10 +380,9 @@ class RequestContext implements IContextSource {
*/
public function getSkin() {
if ( $this->skin === null ) {
- wfProfileIn( __METHOD__ . '-createskin' );
$skin = null;
- wfRunHooks( 'RequestContextCreateSkin', array( $this, &$skin ) );
+ Hooks::run( 'RequestContextCreateSkin', array( $this, &$skin ) );
$factory = SkinFactory::getDefaultInstance();
// If the hook worked try to set a skin from it
@@ -386,7 +417,6 @@ class RequestContext implements IContextSource {
// After all that set a context on whatever skin got created
$this->skin->setContext( $this );
- wfProfileOut( __METHOD__ . '-createskin' );
}
return $this->skin;
@@ -463,10 +493,14 @@ class RequestContext implements IContextSource {
}
/**
- * Import the resolved user IP, HTTP headers, user ID, and session ID.
+ * Import an client IP address, HTTP headers, user ID, and session ID
+ *
* This sets the current session and sets $wgUser and $wgRequest.
* Once the return value falls out of scope, the old context is restored.
- * This function can only be called within CLI mode scripts.
+ * This method should only be called in contexts (CLI or HTTP job runners)
+ * where there is no session ID or end user receiving the response. This
+ * is partly enforced, and is done so to avoid leaking cookies if certain
+ * error conditions arise.
*
* This will setup the session from the given ID. This is useful when
* background scripts inherit context when acting on behalf of a user.
@@ -479,11 +513,12 @@ class RequestContext implements IContextSource {
* @since 1.21
*/
public static function importScopedSession( array $params ) {
- if ( PHP_SAPI !== 'cli' ) {
- // Don't send random private cookies or turn $wgRequest into FauxRequest
- throw new MWException( "Sessions can only be imported in cli mode." );
- } elseif ( !strlen( $params['sessionId'] ) ) {
- throw new MWException( "No session ID was specified." );
+ if ( session_id() != '' && strlen( $params['sessionId'] ) ) {
+ // Sanity check to avoid sending random cookies for the wrong users.
+ // This method should only called by CLI scripts or by HTTP job runners.
+ throw new MWException( "Sessions can only be imported when none is active." );
+ } elseif ( !IP::isValid( $params['ip'] ) ) {
+ throw new MWException( "Invalid client IP address '{$params['ip']}'." );
}
if ( $params['userId'] ) { // logged-in user
@@ -492,13 +527,11 @@ class RequestContext implements IContextSource {
if ( !$user->getId() ) {
throw new MWException( "No user with ID '{$params['userId']}'." );
}
- } elseif ( !IP::isValid( $params['ip'] ) ) {
- throw new MWException( "Could not load user '{$params['ip']}'." );
} else { // anon user
$user = User::newFromName( $params['ip'], false );
}
- $importSessionFunction = function ( User $user, array $params ) {
+ $importSessionFunc = function ( User $user, array $params ) {
global $wgRequest, $wgUser;
$context = RequestContext::getMain();
@@ -531,12 +564,19 @@ class RequestContext implements IContextSource {
// Stash the old session and load in the new one
$oUser = self::getMain()->getUser();
$oParams = self::getMain()->exportSession();
- $importSessionFunction( $user, $params );
+ $oRequest = self::getMain()->getRequest();
+ $importSessionFunc( $user, $params );
// Set callback to save and close the new session and reload the old one
- return new ScopedCallback( function () use ( $importSessionFunction, $oUser, $oParams ) {
- $importSessionFunction( $oUser, $oParams );
- } );
+ return new ScopedCallback(
+ function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) {
+ global $wgRequest;
+ $importSessionFunc( $oUser, $oParams );
+ // Restore the exact previous Request object (instead of leaving FauxRequest)
+ RequestContext::getMain()->setRequest( $oRequest );
+ $wgRequest = RequestContext::getMain()->getRequest(); // b/c
+ }
+ );
}
/**
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 9b783a99..0c0248da 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -26,186 +26,6 @@
*/
/**
- * Base interface for all DBMS-specific code. At a bare minimum, all of the
- * following must be implemented to support MediaWiki
- *
- * @file
- * @ingroup Database
- */
-interface DatabaseType {
- /**
- * Get the type of the DBMS, as it appears in $wgDBtype.
- *
- * @return string
- */
- function getType();
-
- /**
- * 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
- * @return bool
- * @throws DBConnectionError
- */
- function open( $server, $user, $password, $dbName );
-
- /**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- * If no more rows are available, false is returned.
- *
- * @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'].
- * If no more rows are available, false is returned.
- *
- * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
- * @return array|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow( $res );
-
- /**
- * Get the number of rows in a result object
- *
- * @param mixed $res A SQL result
- * @return int
- */
- function numRows( $res );
-
- /**
- * Get the number of fields in a result object
- * @see http://www.php.net/mysql_num_fields
- *
- * @param mixed $res A SQL result
- * @return int
- */
- function numFields( $res );
-
- /**
- * Get a field name in a result object
- * @see http://www.php.net/mysql_field_name
- *
- * @param mixed $res A SQL result
- * @param int $n
- * @return string
- */
- function fieldName( $res, $n );
-
- /**
- * Get the inserted value of an auto-increment row
- *
- * The value inserted should be fetched from nextSequenceValue()
- *
- * Example:
- * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
- * $dbw->insert( 'page', array( 'page_id' => $id ) );
- * $id = $dbw->insertId();
- *
- * @return int
- */
- function insertId();
-
- /**
- * Change the position of the cursor in a result object
- * @see http://www.php.net/mysql_data_seek
- *
- * @param mixed $res A SQL result
- * @param int $row
- */
- function dataSeek( $res, $row );
-
- /**
- * Get the last error number
- * @see http://www.php.net/mysql_errno
- *
- * @return int
- */
- function lastErrno();
-
- /**
- * Get a description of the last error
- * @see http://www.php.net/mysql_error
- *
- * @return string
- */
- function lastError();
-
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param string $table Table name
- * @param string $field Field name
- *
- * @return Field
- */
- function fieldInfo( $table, $field );
-
- /**
- * Get information about an index into an object
- * @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
- */
- function indexInfo( $table, $index, $fname = __METHOD__ );
-
- /**
- * Get the number of rows affected by the last write query
- * @see http://www.php.net/mysql_affected_rows
- *
- * @return int
- */
- function affectedRows();
-
- /**
- * Wrapper for addslashes()
- *
- * @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]";
- * 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
- */
- function getSoftwareLink();
-
- /**
- * A string describing the current software version, like from
- * mysql_get_server_info().
- *
- * @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.
- * Use getServerVersion() to get machine-friendly information.
- *
- * @return string Version information from the database server
- */
- function getServerInfo();
-}
-
-/**
* Interface for classes that implement or wrap DatabaseBase
* @ingroup Database
*/
@@ -216,7 +36,7 @@ interface IDatabase {
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase implements IDatabase, DatabaseType {
+abstract class DatabaseBase implements IDatabase {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
@@ -226,10 +46,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/** Maximum time to wait before retry */
const DEADLOCK_DELAY_MAX = 1500000;
-# ------------------------------------------------------------------------------
-# Variables
-# ------------------------------------------------------------------------------
-
protected $mLastQuery = '';
protected $mDoneWrites = false;
protected $mPHPError = false;
@@ -272,10 +88,21 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* Either a short hexidecimal string if a transaction is active or ""
*
* @var string
+ * @see DatabaseBase::mTrxLevel
*/
protected $mTrxShortId = '';
/**
+ * The UNIX time that the transaction started. Callers can assume that if
+ * snapshot isolation is used, then the data is *at least* up to date to that
+ * point (possibly more up-to-date since the first SELECT defines the snapshot).
+ *
+ * @var float|null
+ * @see DatabaseBase::mTrxLevel
+ */
+ private $mTrxTimestamp = null;
+
+ /**
* Remembers the function name given for starting the most recent transaction via begin().
* Used to provide additional context for error reporting.
*
@@ -326,10 +153,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*/
protected $allViews = null;
-# ------------------------------------------------------------------------------
-# Accessors
-# ------------------------------------------------------------------------------
- # These optionally set a variable and return the previous state
+ /** @var TransactionProfiler */
+ protected $trxProfiler;
/**
* A string describing the current software version, and possibly
@@ -420,6 +245,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Get the UNIX timestamp of the time that the transaction was established
+ *
+ * This can be used to reason about the staleness of SELECT data
+ * in REPEATABLE-READ transaction isolation level.
+ *
+ * @return float|null Returns null if there is not active transaction
+ * @since 1.25
+ */
+ public function trxTimestamp() {
+ return $this->mTrxLevel ? $this->mTrxTimestamp : null;
+ }
+
+ /**
* Get/set the number of errors logged. Only useful when errors are ignored
* @param int $count The count to set, or omitted to leave it unchanged.
* @return int The error count
@@ -511,6 +349,15 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * @return TransactionProfiler
+ */
+ protected function getTransactionProfiler() {
+ return $this->trxProfiler
+ ? $this->trxProfiler
+ : Profiler::instance()->getTransactionProfiler();
+ }
+
+ /**
* Returns true if this database supports (and uses) cascading deletes
*
* @return bool
@@ -744,9 +591,167 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
return $this->getSqlFilePath( 'update-keys.sql' );
}
-# ------------------------------------------------------------------------------
-# Other functions
-# ------------------------------------------------------------------------------
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ *
+ * @return string
+ */
+ abstract function getType();
+
+ /**
+ * 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
+ * @return bool
+ * @throws DBConnectionError
+ */
+ abstract function open( $server, $user, $password, $dbName );
+
+ /**
+ * Fetch the next row from the given result object, in object form.
+ * Fields can be retrieved with $row->fieldname, with fields acting like
+ * member variables.
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc.
+ * @return stdClass|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ abstract function fetchObject( $res );
+
+ /**
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc.
+ * @return array|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ abstract function fetchRow( $res );
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ abstract function numRows( $res );
+
+ /**
+ * Get the number of fields in a result object
+ * @see http://www.php.net/mysql_num_fields
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ abstract function numFields( $res );
+
+ /**
+ * Get a field name in a result object
+ * @see http://www.php.net/mysql_field_name
+ *
+ * @param mixed $res A SQL result
+ * @param int $n
+ * @return string
+ */
+ abstract function fieldName( $res, $n );
+
+ /**
+ * Get the inserted value of an auto-increment row
+ *
+ * The value inserted should be fetched from nextSequenceValue()
+ *
+ * Example:
+ * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+ * $dbw->insert( 'page', array( 'page_id' => $id ) );
+ * $id = $dbw->insertId();
+ *
+ * @return int
+ */
+ abstract function insertId();
+
+ /**
+ * Change the position of the cursor in a result object
+ * @see http://www.php.net/mysql_data_seek
+ *
+ * @param mixed $res A SQL result
+ * @param int $row
+ */
+ abstract function dataSeek( $res, $row );
+
+ /**
+ * Get the last error number
+ * @see http://www.php.net/mysql_errno
+ *
+ * @return int
+ */
+ abstract function lastErrno();
+
+ /**
+ * Get a description of the last error
+ * @see http://www.php.net/mysql_error
+ *
+ * @return string
+ */
+ abstract function lastError();
+
+ /**
+ * mysql_fetch_field() wrapper
+ * Returns false if the field doesn't exist
+ *
+ * @param string $table Table name
+ * @param string $field Field name
+ *
+ * @return Field
+ */
+ abstract function fieldInfo( $table, $field );
+
+ /**
+ * Get information about an index into an object
+ * @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
+ */
+ abstract function indexInfo( $table, $index, $fname = __METHOD__ );
+
+ /**
+ * Get the number of rows affected by the last write query
+ * @see http://www.php.net/mysql_affected_rows
+ *
+ * @return int
+ */
+ abstract function affectedRows();
+
+ /**
+ * Wrapper for addslashes()
+ *
+ * @param string $s String to be slashed.
+ * @return string Slashed string.
+ */
+ abstract function strencode( $s );
+
+ /**
+ * Returns a wikitext link to the DB's website, e.g.,
+ * 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
+ */
+ abstract function getSoftwareLink();
+
+ /**
+ * A string describing the current software version, like from
+ * mysql_get_server_info().
+ *
+ * @return string Version information from the database server.
+ */
+ abstract function getServerVersion();
/**
* Constructor.
@@ -760,32 +765,19 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* @param array $params Parameters passed from DatabaseBase::factory()
*/
- function __construct( $params = null ) {
+ function __construct( array $params ) {
global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
$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;
- }
+ $server = $params['host'];
+ $user = $params['user'];
+ $password = $params['password'];
+ $dbName = $params['dbname'];
+ $flags = $params['flags'];
+ $tablePrefix = $params['tablePrefix'];
+ $schema = $params['schema'];
+ $foreign = $params['foreign'];
$this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
@@ -818,6 +810,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->mForeign = $foreign;
+ if ( isset( $params['trxProfiler'] ) ) {
+ $this->trxProfiler = $params['trxProfiler']; // override
+ }
+
if ( $user ) {
$this->open( $server, $user, $password, $dbName );
}
@@ -905,18 +901,17 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$class = 'Database' . ucfirst( $driver );
if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
- $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 );
+ // Resolve some defaults for b/c
+ $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
+ $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
+ $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
+ $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
+ $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
+ $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global';
+ $p['schema'] = isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType];
+ $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
+
+ return new $class( $p );
} else {
return null;
}
@@ -955,6 +950,23 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Create a log context to pass to wfLogDBError or other logging functions.
+ *
+ * @param array $extras Additional data to add to context
+ * @return array
+ */
+ protected function getLogContext( array $extras = array() ) {
+ return array_merge(
+ array(
+ 'db_server' => $this->mServer,
+ 'db_name' => $this->mDBname,
+ 'db_user' => $this->mUser,
+ ),
+ $extras
+ );
+ }
+
+ /**
* Closes a database connection.
* if it is open : commits any open transactions
*
@@ -1027,6 +1039,20 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * Determine whether a SQL statement is sensitive to isolation level.
+ * A SQL statement is considered transactable if its result could vary
+ * depending on the transaction isolation level. Operational commands
+ * such as 'SET' and 'SHOW' are not considered to be transactable.
+ *
+ * @param string $sql
+ * @return bool
+ */
+ public function isTransactableQuery( $sql ) {
+ $verb = substr( $sql, 0, strcspn( $sql, " \t\r\n" ) );
+ return !in_array( $verb, array( 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ) );
+ }
+
+ /**
* Run an SQL query and return the result. Normally throws a DBQueryError
* on failure. If errors are ignored, returns false instead.
*
@@ -1052,9 +1078,14 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
$this->mLastQuery = $sql;
- if ( $this->isWriteQuery( $sql ) ) {
+
+ $isWriteQuery = $this->isWriteQuery( $sql );
+ if ( $isWriteQuery ) {
+ if ( !$this->mDoneWrites ) {
+ wfDebug( __METHOD__ . ': Writes done: ' .
+ DatabaseBase::generalizeSQL( $sql ) . "\n" );
+ }
# Set a flag indicating that writes have been done
- wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" );
$this->mDoneWrites = microtime( true );
}
@@ -1073,50 +1104,38 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
// Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
$commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
- # If DBO_TRX is set, start a transaction
- if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel &&
- $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
- $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
- if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) {
- if ( $wgDebugDBTransactions ) {
- wfDebug( "Implicit transaction start.\n" );
- }
- $this->begin( __METHOD__ . " ($fname)" );
- $this->mTrxAutomatic = true;
+ if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) && $this->isTransactableQuery( $sql ) ) {
+ if ( $wgDebugDBTransactions ) {
+ wfDebug( "Implicit transaction start.\n" );
}
+ $this->begin( __METHOD__ . " ($fname)" );
+ $this->mTrxAutomatic = true;
}
# Keep track of whether the transaction has write queries pending
- if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) {
+ if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWriteQuery ) {
$this->mTrxDoneWrites = true;
- Profiler::instance()->transactionWritingIn(
+ $this->getTransactionProfiler()->transactionWritingIn(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
- $queryProf = '';
- $totalProf = '';
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
+ # generalizeSQL will probably cut down the query to reasonable
+ # logging size most of the time. The substr is really just a sanity check.
+ if ( $isMaster ) {
+ $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query-master';
+ } else {
+ $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query';
+ }
+ # Include query transaction state
+ $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
- 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.
- if ( $isMaster ) {
- $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'DatabaseBase::query-master';
- } else {
- $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 );
+ $profiler = Profiler::instance();
+ if ( !$profiler instanceof ProfilerStub ) {
+ $totalProfSection = $profiler->scopedProfileIn( $totalProf );
+ $queryProfSection = $profiler->scopedProfileIn( $queryProf );
}
if ( $this->debug() ) {
@@ -1139,7 +1158,11 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
# Do the query and handle errors
+ $startTime = microtime( true );
$ret = $this->doQuery( $commentedSql );
+ # Log the query time and feed it into the DB trx profiler
+ $this->getTransactionProfiler()->recordQueryCompletion(
+ $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
MWDebug::queryTime( $queryId );
@@ -1155,23 +1178,22 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$lastError = $this->lastError();
$lastErrno = $this->lastErrno();
if ( $this->ping() ) {
- global $wgRequestTime;
wfDebug( "Reconnected\n" );
- $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
- : $commentedSql;
- $sqlx = strtr( $sqlx, "\t\n", ' ' );
- $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" );
- }
+ $server = $this->getServer();
+ $msg = __METHOD__ . ": lost connection to $server; reconnected";
+ wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+
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)
+ $startTime = microtime( true );
$ret = $this->doQuery( $commentedSql );
+ # Log the query time and feed it into the DB trx profiler
+ $this->getTransactionProfiler()->recordQueryCompletion(
+ $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
}
} else {
wfDebug( "Failed\n" );
@@ -1179,15 +1201,17 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
if ( false === $ret ) {
- $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
+ $this->reportQueryError(
+ $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
}
- if ( !Profiler::instance()->isStub() ) {
- wfProfileOut( $queryProf );
- wfProfileOut( $totalProf );
- }
+ $res = $this->resultObject( $ret );
+
+ // Destroy profile sections in the opposite order to their creation
+ $queryProfSection = false;
+ $totalProfSection = false;
- return $this->resultObject( $ret );
+ return $res;
}
/**
@@ -1202,16 +1226,22 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @throws DBQueryError
*/
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- # Ignore errors during error handling to avoid infinite recursion
- $ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
- if ( $ignore || $tempIgnore ) {
+ if ( $this->ignoreErrors() || $tempIgnore ) {
wfDebug( "SQL ERROR (ignored): $error\n" );
- $this->ignoreErrors( $ignore );
} else {
$sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
- wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" );
+ wfLogDBError(
+ "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'errno' => $errno,
+ 'error' => $error,
+ 'sql1line' => $sql1line,
+ 'fname' => $fname,
+ ) )
+ );
wfDebug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
@@ -1348,9 +1378,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* @return bool|mixed The value from the field, or false on failure.
*/
- public function selectField( $table, $var, $cond = '', $fname = __METHOD__,
- $options = array()
+ public function selectField(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = array()
) {
+ if ( $var === '*' ) { // sanity
+ throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+ }
+
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -1358,7 +1392,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$options['LIMIT'] = 1;
$res = $this->select( $table, $var, $cond, $fname, $options );
-
if ( $res === false || !$this->numRows( $res ) ) {
return false;
}
@@ -1373,6 +1406,48 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
+ * A SELECT wrapper which returns a list of single field values from result rows.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly
+ * ignored, returns false on failure.
+ *
+ * If no result rows are returned from the query, false is returned.
+ *
+ * @param string|array $table Table name. See DatabaseBase::select() for details.
+ * @param string $var The field name to select. This must be a valid SQL
+ * fragment: do not use unvalidated user input.
+ * @param string|array $cond The condition array. See DatabaseBase::select() for details.
+ * @param string $fname The function name of the caller.
+ * @param string|array $options The query options. See DatabaseBase::select() for details.
+ *
+ * @return bool|array The values from the field, or false on failure
+ * @since 1.25
+ */
+ public function selectFieldValues(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = array()
+ ) {
+ if ( $var === '*' ) { // sanity
+ throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
+ }
+
+ if ( !is_array( $options ) ) {
+ $options = array( $options );
+ }
+
+ $res = $this->select( $table, $var, $cond, $fname, $options );
+ if ( $res === false ) {
+ return false;
+ }
+
+ $values = array();
+ foreach ( $res as $row ) {
+ $values[] = $row->$var;
+ }
+
+ return $values;
+ }
+
+ /**
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query.
*
@@ -1558,9 +1633,10 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* - If the value of such an array element is a scalar (such as a
* string), it will be treated as data and thus quoted appropriately.
* If it is null, an IS NULL clause will be added.
- * - If the value is an array, an IN(...) clause will be constructed,
- * such that the field name may match any of the elements in the
- * array. The elements of the array will be quoted.
+ * - If the value is an array, an IN (...) clause will be constructed
+ * from its non-null elements, and an IS NULL clause will be added
+ * if null is present, such that the field may match any of the
+ * elements in the array. The non-null elements will be quoted.
*
* Note that expressions are often DBMS-dependent in their syntax.
* DBMS-independent wrappers are provided for constructing several types of
@@ -1779,7 +1855,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $res ) {
$row = $this->fetchRow( $res );
- $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+ $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
}
return $rows;
@@ -1809,7 +1885,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
if ( $res ) {
$row = $this->fetchRow( $res );
- $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
+ $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
}
return $rows;
@@ -2117,16 +2193,36 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
} elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
$list .= "$value";
} elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
- if ( count( $value ) == 0 ) {
+ // Remove null from array to be handled separately if found
+ $includeNull = false;
+ foreach ( array_keys( $value, null, true ) as $nullKey ) {
+ $includeNull = true;
+ unset( $value[$nullKey] );
+ }
+ if ( count( $value ) == 0 && !$includeNull ) {
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] );
+ } elseif ( count( $value ) == 0 ) {
+ // only check if $field is null
+ $list .= "$field IS NULL";
} else {
- $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
+ // IN clause contains at least one valid element
+ if ( $includeNull ) {
+ // Group subconditions to ensure correct precedence
+ $list .= '(';
+ }
+ if ( 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 ) . ") ";
+ }
+ // if null present in array, append IS NULL
+ if ( $includeNull ) {
+ $list .= " OR $field IS NULL)";
+ }
}
} elseif ( $value === null ) {
if ( $mode == LIST_AND || $mode == LIST_OR ) {
@@ -2322,7 +2418,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
# Split database and table into proper variables.
# We reverse the explode so that database.table and table both output
# the correct table.
- $dbDetails = explode( '.', $name, 2 );
+ $dbDetails = explode( '.', $name, 3 );
if ( count( $dbDetails ) == 3 ) {
list( $database, $schema, $table ) = $dbDetails;
# We don't want any prefix added in this case
@@ -2552,12 +2648,16 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Get the name of an index in a given table
+ * Get the name of an index in a given table.
*
+ * @protected Don't use outside of DatabaseBase and childs
* @param string $index
* @return string
*/
- protected function indexName( $index ) {
+ public function indexName( $index ) {
+ // @FIXME: Make this protected once we move away from PHP 5.3
+ // Needs to be public because of usage in closure (in DatabaseBase::replaceVars)
+
// Backwards-compatibility hack
$renamed = array(
'ar_usertext_timestamp' => 'usertext_timestamp',
@@ -2575,10 +2675,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
/**
* Adds quotes and backslashes.
*
- * @param string $s
+ * @param string|Blob $s
* @return string
*/
public function addQuotes( $s ) {
+ if ( $s instanceof Blob ) {
+ $s = $s->fetch();
+ }
if ( $s === null ) {
return 'NULL';
} else {
@@ -3218,41 +3321,40 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* @return bool
*/
public function deadlockLoop() {
- $this->begin( __METHOD__ );
$args = func_get_args();
$function = array_shift( $args );
- $oldIgnore = $this->ignoreErrors( true );
$tries = self::DEADLOCK_TRIES;
-
if ( is_array( $function ) ) {
$fname = $function[0];
} else {
$fname = $function;
}
- do {
- $retVal = call_user_func_array( $function, $args );
- $error = $this->lastError();
- $errno = $this->lastErrno();
- $sql = $this->lastQuery();
+ $this->begin( __METHOD__ );
- if ( $errno ) {
+ $e = null;
+ do {
+ try {
+ $retVal = call_user_func_array( $function, $args );
+ break;
+ } catch ( DBQueryError $e ) {
+ $error = $this->lastError();
+ $errno = $this->lastErrno();
+ $sql = $this->lastQuery();
if ( $this->wasDeadlock() ) {
- # Retry
+ // Retry after a randomized delay
usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
} else {
- $this->reportQueryError( $error, $errno, $sql, $fname );
+ // Throw the error back up
+ throw $e;
}
}
- } while ( $this->wasDeadlock() && --$tries > 0 );
-
- $this->ignoreErrors( $oldIgnore );
+ } while ( --$tries > 0 );
if ( $tries <= 0 ) {
+ // Too many deadlocks; give up
$this->rollback( __METHOD__ );
- $this->reportQueryError( $error, $errno, $sql, $fname );
-
- return false;
+ throw $e;
} else {
$this->commit( __METHOD__ );
@@ -3486,7 +3588,12 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
" performing implicit commit!";
wfWarn( $msg );
- wfLogDBError( $msg );
+ wfLogDBError( $msg,
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'fname' => $fname,
+ ) )
+ );
} else {
// if the transaction was automatic and has done write operations,
// log it if $wgDebugDBTransactions is enabled.
@@ -3500,7 +3607,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ $this->mDoneWrites = microtime( true );
+ $this->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
@@ -3512,6 +3620,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
$this->doBegin( $fname );
+ $this->mTrxTimestamp = microtime( true );
$this->mTrxFname = $fname;
$this->mTrxDoneWrites = false;
$this->mTrxAutomatic = false;
@@ -3579,7 +3688,8 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->runOnTransactionPreCommitCallbacks();
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ $this->mDoneWrites = microtime( true );
+ $this->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
$this->runOnTransactionIdleCallbacks();
@@ -3609,6 +3719,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* calling rollback when no transaction is in progress. This will silently
* break any ongoing explicit transaction. Only set the flush flag if you
* are sure that it is safe to ignore these warnings in your context.
+ * @throws DBUnexpectedError
* @since 1.23 Added $flush parameter
*/
final public function rollback( $fname = __METHOD__, $flush = '' ) {
@@ -3637,7 +3748,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
$this->mTrxPreCommitCallbacks = array(); // cancel
$this->mTrxAtomicLevels = new SplStack;
if ( $this->mTrxDoneWrites ) {
- Profiler::instance()->transactionWritingOut(
+ $this->getTransactionProfiler()->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
}
@@ -3835,10 +3946,13 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
* in result objects. Pass the object through this function to return the
* original string.
*
- * @param string $b
+ * @param string|Blob $b
* @return string
*/
public function decodeBlob( $b ) {
+ if ( $b instanceof Blob ) {
+ $b = $b->fetch();
+ }
return $b;
}
@@ -3888,7 +4002,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
try {
$error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
fclose( $fp );
throw $e;
}
@@ -4019,47 +4133,49 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
*
* - '{$var}' should be used for text and is passed through the database's
* addQuotes method.
- * - `{$var}` should be used for identifiers (eg: table and database names),
- * it is passed through the database's addIdentifierQuotes method which
+ * - `{$var}` should be used for identifiers (e.g. table and database names).
+ * It is passed through the database's addIdentifierQuotes method which
* can be overridden if the database uses something other than backticks.
- * - / *$var* / is just encoded, besides traditional table prefix and
- * table options its use should be avoided.
+ * - / *_* / or / *$wgDBprefix* / passes the name that follows through the
+ * database's tableName method.
+ * - / *i* / passes the name that follows through the database's indexName method.
+ * - In all other cases, / *$var* / is left unencoded. Except for table options,
+ * its use should be avoided. In 1.24 and older, string encoding was applied.
*
* @param string $ins SQL statement to replace variables in
* @return string The new SQL statement with variables replaced
*/
- protected function replaceSchemaVars( $ins ) {
- $vars = $this->getSchemaVars();
- foreach ( $vars as $var => $value ) {
- // replace '{$var}'
- $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
- // replace `{$var}`
- $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
- // replace /*$var*/
- $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins );
- }
-
- return $ins;
- }
-
- /**
- * Replace variables in sourced SQL
- *
- * @param string $ins
- * @return string
- */
protected function replaceVars( $ins ) {
- $ins = $this->replaceSchemaVars( $ins );
-
- // Table prefixes
- $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
- array( $this, 'tableNameCallback' ), $ins );
-
- // Index names
- $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
- array( $this, 'indexNameCallback' ), $ins );
-
- return $ins;
+ $that = $this;
+ $vars = $this->getSchemaVars();
+ return preg_replace_callback(
+ '!
+ /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
+ \'\{\$ (\w+) }\' | # 3. addQuotes
+ `\{\$ (\w+) }` | # 4. addIdentifierQuotes
+ /\*\$ (\w+) \*/ # 5. leave unencoded
+ !x',
+ function ( $m ) use ( $that, $vars ) {
+ // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
+ // check for both nonexistent keys *and* the empty string.
+ if ( isset( $m[1] ) && $m[1] !== '' ) {
+ if ( $m[1] === 'i' ) {
+ return $that->indexName( $m[2] );
+ } else {
+ return $that->tableName( $m[2] );
+ }
+ } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
+ return $that->addQuotes( $vars[$m[3]] );
+ } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
+ return $that->addIdentifierQuotes( $vars[$m[4]] );
+ } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
+ return $vars[$m[5]];
+ } else {
+ return $m[0];
+ }
+ },
+ $ins
+ );
}
/**
@@ -4089,26 +4205,6 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
}
/**
- * Table name callback
- *
- * @param array $matches
- * @return string
- */
- protected function tableNameCallback( $matches ) {
- return $this->tableName( $matches[1] );
- }
-
- /**
- * Index name callback
- *
- * @param array $matches
- * @return string
- */
- protected function indexNameCallback( $matches ) {
- return $this->indexName( $matches[1] );
- }
-
- /**
* Check to see if a named lock is available. This is non-blocking.
*
* @param string $lockName Name of lock to poll
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index 2dfec41d..86950a89 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -168,12 +168,12 @@ class DBConnectionError extends DBExpectedError {
if ( $wgShowHostnames || $wgShowSQLErrors ) {
$info = str_replace(
'$1', Html::element( 'span', array( 'dir' => 'ltr' ), $this->error ),
- htmlspecialchars( $this->msg( 'dberr-info', '(Cannot contact the database server: $1)' ) )
+ htmlspecialchars( $this->msg( 'dberr-info', '(Cannot access the database: $1)' ) )
);
} else {
$info = htmlspecialchars( $this->msg(
'dberr-info-hidden',
- '(Cannot contact the database server)'
+ '(Cannot access the database)'
) );
}
@@ -229,7 +229,7 @@ class DBConnectionError extends DBExpectedError {
return;
}
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// Do nothing, just use the default page
}
}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index af3cc72d..2b8f395f 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -380,6 +380,9 @@ class DatabaseMssql extends DatabaseBase {
* (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
+ * @throws DBQueryError
+ * @throws DBUnexpectedError
+ * @throws Exception
*/
public function select( $table, $vars, $conds = '', $fname = __METHOD__,
$options = array(), $join_conds = array()
@@ -515,7 +518,7 @@ class DatabaseMssql extends DatabaseBase {
$row = $this->fetchRow( $res );
if ( isset( $row['EstimateRows'] ) ) {
- $rows = $row['EstimateRows'];
+ $rows = (int)$row['EstimateRows'];
}
}
@@ -574,8 +577,8 @@ class DatabaseMssql extends DatabaseBase {
* @param array $arrToInsert
* @param string $fname
* @param array $options
- * @throws DBQueryError
* @return bool
+ * @throws Exception
*/
public function insert( $table, $arrToInsert, $fname = __METHOD__, $options = array() ) {
# No rows to insert, easy just return now
@@ -692,7 +695,7 @@ class DatabaseMssql extends DatabaseBase {
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 ) ){
+ if ( is_object( $row ) ) {
$this->mInsertId = $row->$identity;
}
}
@@ -713,8 +716,8 @@ class DatabaseMssql extends DatabaseBase {
* @param string $fname
* @param array $insertOptions
* @param array $selectOptions
- * @throws DBQueryError
* @return null|ResultWrapper
+ * @throws Exception
*/
public function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
$insertOptions = array(), $selectOptions = array()
@@ -761,6 +764,9 @@ class DatabaseMssql extends DatabaseBase {
* - IGNORE: Ignore unique key conflicts
* - LOW_PRIORITY: MySQL-specific, see MySQL manual.
* @return bool
+ * @throws DBUnexpectedError
+ * @throws Exception
+ * @throws MWException
*/
function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
$table = $this->tableName( $table );
@@ -806,64 +812,27 @@ class DatabaseMssql extends DatabaseBase {
'DatabaseBase::makeList called with incorrect parameters' );
}
- $first = true;
- $list = '';
+ if ( $mode != LIST_NAMES ) {
+ // In MS SQL, values need to be specially encoded when they are
+ // inserted into binary fields. Perform this necessary encoding
+ // for the specified set of columns.
+ foreach ( array_keys( $a ) as $field ) {
+ if ( !isset( $binaryColumns[$field] ) ) {
+ continue;
+ }
- foreach ( $a as $field => $value ) {
- if ( $mode != LIST_NAMES && isset( $binaryColumns[$field] ) ) {
- if ( is_array( $value ) ) {
- foreach ( $value as &$v ) {
+ if ( is_array( $a[$field] ) ) {
+ foreach ( $a[$field] as &$v ) {
$v = new MssqlBlob( $v );
}
+ unset( $v );
} else {
- $value = new MssqlBlob( $value );
- }
- }
-
- if ( !$first ) {
- if ( $mode == LIST_AND ) {
- $list .= ' AND ';
- } elseif ( $mode == LIST_OR ) {
- $list .= ' OR ';
- } else {
- $list .= ',';
+ $a[$field] = new MssqlBlob( $a[$field] );
}
- } 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;
+ return parent::makeList( $a, $mode );
}
/**
@@ -893,6 +862,7 @@ class DatabaseMssql extends DatabaseBase {
* @param int $limit The SQL limit
* @param bool|int $offset The SQL offset (default false)
* @return array|string
+ * @throws DBUnexpectedError
*/
public function limitResult( $sql, $limit, $offset = false ) {
if ( $offset === false || $offset == 0 ) {
@@ -953,12 +923,8 @@ class DatabaseMssql extends DatabaseBase {
// 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 ) ) {
- // row_count = $matches[4]
$row_count = $matches[4];
- // offset = $matches[3] OR $matches[6]
- $offset = $matches[3] or
- $offset = $matches[6] or
- $offset = false;
+ $offset = $matches[3] ?: $matches[6] ?: false;
// strip the matching LIMIT clause out
$sql = str_replace( $matches[0], '', $sql );
@@ -1128,7 +1094,7 @@ class DatabaseMssql extends DatabaseBase {
}
/**
- * @param string $s
+ * @param string|Blob $s
* @return string
*/
public function addQuotes( $s ) {
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index dc4a67df..823d9b67 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -142,13 +142,6 @@ class DatabaseMysql extends DatabaseMysqlBase {
return mysql_select_db( $db, $this->mConn );
}
- /**
- * @return string
- */
- function getServerVersion() {
- return mysql_get_server_info( $this->mConn );
- }
-
protected function mysqlFreeResult( $res ) {
return mysql_free_result( $res );
}
diff --git a/includes/db/DatabaseMysqlBase.php b/includes/db/DatabaseMysqlBase.php
index ba0f39ff..aac95a8c 100644
--- a/includes/db/DatabaseMysqlBase.php
+++ b/includes/db/DatabaseMysqlBase.php
@@ -38,6 +38,9 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
protected $mFakeMaster = false;
+ /** @var string|null */
+ private $serverVersion = null;
+
/**
* @return string
*/
@@ -55,7 +58,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
function open( $server, $user, $password, $dbName ) {
global $wgAllDBsAreLocalhost, $wgSQLMode;
- wfProfileIn( __METHOD__ );
# Debugging hack -- fake cluster
if ( $wgAllDBsAreLocalhost ) {
@@ -69,8 +71,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->mPassword = $password;
$this->mDBname = $dbName;
- wfProfileIn( "dbconnect-$server" );
-
# The kernel's default SYN retransmission period is far too slow for us,
# so we use a short timeout plus a manual retry. Retrying means that a small
# but finite rate of SYN packet loss won't cause user-visible errors.
@@ -79,27 +79,27 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
try {
$this->mConn = $this->mysqlConnect( $realServer );
} catch ( Exception $ex ) {
- wfProfileOut( "dbconnect-$server" );
- wfProfileOut( __METHOD__ );
$this->restoreErrorHandler();
throw $ex;
}
$error = $this->restoreErrorHandler();
- wfProfileOut( "dbconnect-$server" );
-
# Always log connection errors
if ( !$this->mConn ) {
if ( !$error ) {
$error = $this->lastError();
}
- wfLogDBError( "Error connecting to {$this->mServer}: $error" );
+ wfLogDBError(
+ "Error connecting to {db_server}: {error}",
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ 'error' => $error,
+ ) )
+ );
wfDebug( "DB connection error\n" .
"Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
- wfProfileOut( __METHOD__ );
-
$this->reportConnectionError( $error );
}
@@ -108,12 +108,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$success = $this->selectDB( $dbName );
wfRestoreWarnings();
if ( !$success ) {
- wfLogDBError( "Error selecting database $dbName on server {$this->mServer}" );
+ wfLogDBError(
+ "Error selecting database {db_name} on server {db_server}",
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ ) )
+ );
wfDebug( "Error selecting database $dbName on server {$this->mServer} " .
"from client host " . wfHostname() . "\n" );
- wfProfileOut( __METHOD__ );
-
$this->reportConnectionError( "Error selecting database $dbName" );
}
}
@@ -123,20 +126,29 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$this->reportConnectionError( "Error setting character set" );
}
+ // Abstract over any insane MySQL defaults
+ $set = array( 'group_concat_max_len = 262144' );
// Set SQL mode, default is turning them all off, can be overridden or skipped with null
if ( is_string( $wgSQLMode ) ) {
- $mode = $this->addQuotes( $wgSQLMode );
+ $set[] = 'sql_mode = ' . $this->addQuotes( $wgSQLMode );
+ }
+
+ if ( $set ) {
// Use doQuery() to avoid opening implicit transactions (DBO_TRX)
- $success = $this->doQuery( "SET sql_mode = $mode", __METHOD__ );
+ $success = $this->doQuery( 'SET ' . implode( ', ', $set ), __METHOD__ );
if ( !$success ) {
- wfLogDBError( "Error setting sql_mode to $mode on server {$this->mServer}" );
- wfProfileOut( __METHOD__ );
- $this->reportConnectionError( "Error setting sql_mode to $mode" );
+ wfLogDBError(
+ 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
+ $this->getLogContext( array(
+ 'method' => __METHOD__,
+ ) )
+ );
+ $this->reportConnectionError(
+ 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
}
}
$this->mOpened = true;
- wfProfileOut( __METHOD__ );
return true;
}
@@ -456,7 +468,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
$rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
}
- return $rows;
+ return (int)$rows;
}
/**
@@ -652,7 +664,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
return '0'; // http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
}
- wfProfileIn( __METHOD__ );
# Commit any open transactions
$this->commit( __METHOD__, 'flush' );
@@ -661,18 +672,15 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
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;
}
@@ -692,8 +700,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
}
- wfProfileOut( __METHOD__ );
-
return $status;
}
@@ -763,9 +769,9 @@ 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).
+ // MariaDB includes its name in its version string; this is how MariaDB's version of
+ // the mysql command-line client identifies MariaDB servers (see mariadb_connection()
+ // in libmysql/libmysql.c).
$version = $this->getServerVersion();
if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
return '[{{int:version-db-mariadb-url}} MariaDB]';
@@ -778,6 +784,19 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
}
/**
+ * @return string
+ */
+ public function getServerVersion() {
+ // Not using mysql_get_server_info() or similar for consistency: in the handshake,
+ // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
+ // it off (see RPL_VERSION_HACK in include/mysql_com.h).
+ if ( $this->serverVersion === null ) {
+ $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
+ }
+ return $this->serverVersion;
+ }
+
+ /**
* @param array $options
*/
public function setSessionOptions( array $options ) {
@@ -1169,7 +1188,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
*/
class MySQLField implements Field {
private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary;
+ $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
+ $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
function __construct( $info ) {
$this->name = $info->name;
@@ -1183,6 +1203,10 @@ class MySQLField implements Field {
$this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
$this->type = $info->type;
$this->binary = isset( $info->binary ) ? $info->binary : false;
+ $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
+ $this->is_blob = isset( $info->blob ) ? $info->blob : false;
+ $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
+ $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
}
/**
@@ -1231,21 +1255,54 @@ class MySQLField implements Field {
return $this->is_multiple;
}
+ /**
+ * @return bool
+ */
function isBinary() {
return $this->binary;
}
+
+ /**
+ * @return bool
+ */
+ function isNumeric() {
+ return $this->is_numeric;
+ }
+
+ /**
+ * @return bool
+ */
+ function isBlob() {
+ return $this->is_blob;
+ }
+
+ /**
+ * @return bool
+ */
+ function isUnsigned() {
+ return $this->is_unsigned;
+ }
+
+ /**
+ * @return bool
+ */
+ function isZerofill() {
+ return $this->is_zerofill;
+ }
}
class MySQLMasterPos implements DBMasterPos {
/** @var string */
public $file;
-
- /** @var int Timestamp */
+ /** @var int Position */
public $pos;
+ /** @var float UNIX timestamp */
+ public $asOfTime = 0.0;
function __construct( $file, $pos ) {
$this->file = $file;
$this->pos = $pos;
+ $this->asOfTime = microtime( true );
}
function __toString() {
@@ -1271,4 +1328,8 @@ class MySQLMasterPos implements DBMasterPos {
return ( $thisPos && $thatPos && $thisPos >= $thatPos );
}
+
+ function asOfTime() {
+ return $this->asOfTime;
+ }
}
diff --git a/includes/db/DatabaseMysqli.php b/includes/db/DatabaseMysqli.php
index a03c9aaf..ad12e196 100644
--- a/includes/db/DatabaseMysqli.php
+++ b/includes/db/DatabaseMysqli.php
@@ -166,13 +166,6 @@ class DatabaseMysqli extends DatabaseMysqlBase {
}
/**
- * @return string
- */
- function getServerVersion() {
- return $this->mConn->server_info;
- }
-
- /**
* @param mysqli $res
* @return bool
*/
@@ -231,11 +224,18 @@ class DatabaseMysqli extends DatabaseMysqlBase {
*/
protected function mysqlFetchField( $res, $n ) {
$field = $res->fetch_field_direct( $n );
+
+ // Add missing properties to result (using flags property)
+ // which will be part of function mysql-fetch-field for backward compatibility
$field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
$field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
$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;
+ $field->numeric = $field->flags & MYSQLI_NUM_FLAG;
+ $field->blob = $field->flags & MYSQLI_BLOB_FLAG;
+ $field->unsigned = $field->flags & MYSQLI_UNSIGNED_FLAG;
+ $field->zerofill = $field->flags & MYSQLI_ZEROFILL_FLAG;
return $field;
}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index f031f780..9b00fbd1 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -207,29 +207,15 @@ class DatabaseOracle extends DatabaseBase {
/** @var array */
private $mFieldInfoCache = array();
- function __construct( $p = null ) {
+ function __construct( array $p ) {
global $wgDBprefix;
- 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 ) );
+ Hooks::run( 'DatabaseOraclePostInit', array( $this ) );
}
function __destruct() {
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index ce14d7a9..9287f7a6 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -530,10 +530,12 @@ class DatabasePostgres extends DatabaseBase {
return;
}
}
- /* Transaction stays in the ERROR state until rolledback */
+ /* Transaction stays in the ERROR state until rolled back */
if ( $this->mTrxLevel ) {
+ $ignore = $this->ignoreErrors( true );
$this->rollback( __METHOD__ );
- };
+ $this->ignoreErrors( $ignore );
+ }
parent::reportQueryError( $error, $errno, $sql, $fname, false );
}
@@ -712,7 +714,7 @@ class DatabasePostgres extends DatabaseBase {
$row = $this->fetchRow( $res );
$count = array();
if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
- $rows = $count[1];
+ $rows = (int)$count[1];
}
}
@@ -1495,12 +1497,14 @@ SQL;
* @return Blob
*/
function encodeBlob( $b ) {
- return new Blob( pg_escape_bytea( $this->mConn, $b ) );
+ return new PostgresBlob( pg_escape_bytea( $b ) );
}
function decodeBlob( $b ) {
- if ( $b instanceof Blob ) {
+ if ( $b instanceof PostgresBlob ) {
$b = $b->fetch();
+ } elseif ( $b instanceof Blob ) {
+ return $b->fetch();
}
return pg_unescape_bytea( $b );
@@ -1520,7 +1524,12 @@ SQL;
} elseif ( is_bool( $s ) ) {
return intval( $s );
} elseif ( $s instanceof Blob ) {
- return "'" . $s->fetch( $s ) . "'";
+ if ( $s instanceof PostgresBlob ) {
+ $s = $s->fetch();
+ } else {
+ $s = pg_escape_bytea( $this->mConn, $s->fetch() );
+ }
+ return "'$s'";
}
return "'" . pg_escape_string( $this->mConn, $s ) . "'";
@@ -1692,3 +1701,5 @@ SQL;
return wfBaseConvert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
}
} // end DatabasePostgres class
+
+class PostgresBlob extends Blob {}
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index dd2e813e..ed86bab1 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -29,8 +29,14 @@ class DatabaseSqlite extends DatabaseBase {
/** @var bool Whether full text is enabled */
private static $fulltextEnabled = null;
+ /** @var string Directory */
+ protected $dbDir;
+
/** @var string File name for SQLite database file */
- public $mDatabaseFile;
+ protected $dbPath;
+
+ /** @var string Transaction mode */
+ protected $trxMode;
/** @var int The number of rows affected as an integer */
protected $mAffectedRows;
@@ -44,35 +50,63 @@ class DatabaseSqlite extends DatabaseBase {
/** @var FSLockManager (hopefully on the same server as the DB) */
protected $lockMgr;
- function __construct( $p = null ) {
+ /**
+ * Additional params include:
+ * - dbDirectory : directory containing the DB and the lock file directory
+ * [defaults to $wgSQLiteDataDir]
+ * - dbFilePath : use this to force the path of the DB file
+ * - trxMode : one of (deferred, immediate, exclusive)
+ * @param array $p
+ */
+ function __construct( array $p ) {
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 ( $p['dbname'] && !$this->isOpen() ) {
- if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) {
- if ( $wgSharedDB ) {
- $this->attachDatabase( $wgSharedDB );
+ $this->dbDir = isset( $p['dbDirectory'] ) ? $p['dbDirectory'] : $wgSQLiteDataDir;
+
+ if ( isset( $p['dbFilePath'] ) ) {
+ $this->mFlags = isset( $p['flags'] ) ? $p['flags'] : 0;
+ // Standalone .sqlite file mode
+ $this->openFile( $p['dbFilePath'] );
+ // @FIXME: clean up base constructor so this can call super instead
+ $this->mTrxAtomicLevels = new SplStack;
+ } else {
+ $this->mDBname = $p['dbname'];
+ // Stock wiki mode using standard file names per DB
+ parent::__construct( $p );
+ // parent doesn't open when $user is false, but we can work with $dbName
+ 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" ) );
+ $this->trxMode = isset( $p['trxMode'] ) ? strtoupper( $p['trxMode'] ) : null;
+ if ( $this->trxMode &&
+ !in_array( $this->trxMode, array( 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ) )
+ ) {
+ $this->trxMode = null;
+ wfWarn( "Invalid SQLite transaction mode provided." );
+ }
+
+ $this->lockMgr = new FSLockManager( array( 'lockDirectory' => "{$this->dbDir}/locks" ) );
+ }
+
+ /**
+ * @param string $filename
+ * @param array $p Options map; supports:
+ * - flags : (same as __construct counterpart)
+ * - trxMode : (same as __construct counterpart)
+ * - dbDirectory : (same as __construct counterpart)
+ * @return DatabaseSqlite
+ * @since 1.25
+ */
+ public static function newStandaloneInstance( $filename, array $p = array() ) {
+ $p['dbFilePath'] = $filename;
+
+ return new self( $p );
}
/**
@@ -103,10 +137,8 @@ class DatabaseSqlite extends DatabaseBase {
* @return PDO
*/
function open( $server, $user, $pass, $dbName ) {
- global $wgSQLiteDataDir;
-
$this->close();
- $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
+ $fileName = self::generateFileName( $this->dbDir, $dbName );
if ( !is_readable( $fileName ) ) {
$this->mConn = false;
throw new DBConnectionError( $this, "SQLite database not accessible" );
@@ -123,10 +155,10 @@ class DatabaseSqlite extends DatabaseBase {
* @throws DBConnectionError
* @return PDO|bool SQL connection or false if failed
*/
- function openFile( $fileName ) {
+ protected function openFile( $fileName ) {
$err = false;
- $this->mDatabaseFile = $fileName;
+ $this->dbPath = $fileName;
try {
if ( $this->mFlags & DBO_PERSISTENT ) {
$this->mConn = new PDO( "sqlite:$fileName", '', '',
@@ -144,8 +176,8 @@ class DatabaseSqlite extends DatabaseBase {
}
$this->mOpened = !!$this->mConn;
- # set error codes only, don't raise exceptions
if ( $this->mOpened ) {
+ # Set error codes only, don't raise exceptions
$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' );
@@ -157,6 +189,14 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
+ * @return string SQLite DB file path
+ * @since 1.25
+ */
+ public function getDbFilePath() {
+ return $this->dbPath;
+ }
+
+ /**
* Does not actually close the connection, just destroys the reference for GC to do its work
* @return bool
*/
@@ -206,8 +246,7 @@ class DatabaseSqlite extends DatabaseBase {
$cachedResult = false;
$table = 'dummy_search_test';
- $db = new DatabaseSqliteStandalone( ':memory:' );
-
+ $db = self::newStandaloneInstance( ':memory:' );
if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
$cachedResult = 'FTS3';
}
@@ -223,14 +262,13 @@ class DatabaseSqlite extends DatabaseBase {
* @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
+ * using $name and configured data directory
* @param string $fname Calling function name
* @return ResultWrapper
*/
function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
- global $wgSQLiteDataDir;
if ( !$file ) {
- $file = self::generateFileName( $wgSQLiteDataDir, $name );
+ $file = self::generateFileName( $this->dbDir, $name );
}
$file = $this->addQuotes( $file );
@@ -712,37 +750,14 @@ class DatabaseSqlite extends DatabaseBase {
}
protected function doBegin( $fname = '' ) {
- if ( $this->mTrxLevel == 1 ) {
- $this->commit( __METHOD__ );
- }
- try {
- $this->mConn->beginTransaction();
- } catch ( PDOException $e ) {
- throw new DBUnexpectedError( $this, 'Error in BEGIN query: ' . $e->getMessage() );
+ if ( $this->trxMode ) {
+ $this->query( "BEGIN {$this->trxMode}", $fname );
+ } else {
+ $this->query( 'BEGIN', $fname );
}
$this->mTrxLevel = 1;
}
- protected function doCommit( $fname = '' ) {
- if ( $this->mTrxLevel == 0 ) {
- return;
- }
- try {
- $this->mConn->commit();
- } catch ( PDOException $e ) {
- throw new DBUnexpectedError( $this, 'Error in COMMIT query: ' . $e->getMessage() );
- }
- $this->mTrxLevel = 0;
- }
-
- protected function doRollback( $fname = '' ) {
- if ( $this->mTrxLevel == 0 ) {
- return;
- }
- $this->mConn->rollBack();
- $this->mTrxLevel = 0;
- }
-
/**
* @param string $s
* @return string
@@ -787,6 +802,10 @@ class DatabaseSqlite extends DatabaseBase {
// https://bugs.php.net/bug.php?id=63419
// There was already a similar report for SQLite3::escapeString, bug #62361:
// https://bugs.php.net/bug.php?id=62361
+ // There is an additional bug regarding sorting this data after insert
+ // on older versions of sqlite shipped with ubuntu 12.04
+ // https://bugzilla.wikimedia.org/show_bug.cgi?id=72367
+ wfDebugLog( __CLASS__, __FUNCTION__ . ': Quoting value containing null byte. For consistency all binary data should have been first processed with self::encodeBlob()' );
return "x'" . bin2hex( $s ) . "'";
} else {
return $this->mConn->quote( $s );
@@ -882,11 +901,9 @@ class DatabaseSqlite extends DatabaseBase {
}
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\"." );
+ if ( !is_dir( "{$this->dbDir}/locks" ) ) { // create dir as needed
+ if ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) ) {
+ throw new DBError( "Cannot create directory \"{$this->dbDir}/locks\"." );
}
}
@@ -981,18 +998,6 @@ class DatabaseSqlite extends DatabaseBase {
} // end DatabaseSqlite class
/**
- * This class allows simple acccess to a SQLite database independently from main database settings
- * @ingroup Database
- */
-class DatabaseSqliteStandalone extends DatabaseSqlite {
- public function __construct( $fileName, $flags = 0 ) {
- $this->mFlags = $flags;
- $this->tablePrefix( null );
- $this->openFile( $fileName );
- }
-}
-
-/**
* @ingroup Database
*/
class SQLiteField implements Field {
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index c1e80d33..9a520ff9 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -22,29 +22,6 @@
*/
/**
- * Utility class.
- * @ingroup Database
- */
-class DBObject {
- public $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- /**
- * @return bool
- */
- function isLOB() {
- return false;
- }
-
- function data() {
- return $this->mData;
- }
-}
-
-/**
* Utility class
* @ingroup Database
*
@@ -339,4 +316,8 @@ class LikeMatch {
* The implementation details of this opaque type are up to the database subclass.
*/
interface DBMasterPos {
+ /**
+ * @return float UNIX timestamp
+ */
+ public function asOfTime();
}
diff --git a/includes/db/IORMTable.php b/includes/db/IORMTable.php
index 4dc693ac..b2527f95 100644
--- a/includes/db/IORMTable.php
+++ b/includes/db/IORMTable.php
@@ -94,7 +94,7 @@ interface IORMTable {
public function getSummaryFields();
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @see DatabaseBase::select()
@@ -113,7 +113,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @since 1.20
@@ -145,7 +145,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as associative arrays.
* Provided field names get prefixed.
* Returned field names will not have a prefix.
@@ -170,7 +170,7 @@ interface IORMTable {
array $options = array(), $collapse = true, $functionName = null );
/**
- * Selects the the specified fields of the first matching record.
+ * Selects the specified fields of the first matching record.
* Field names get prefixed.
*
* @since 1.20
@@ -186,7 +186,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions. Field names do NOT get prefixed.
*
* @since 1.20
@@ -202,7 +202,7 @@ interface IORMTable {
array $options = array(), $functionName = null );
/**
- * Selects the the specified fields of the first record matching the provided
+ * Selects the specified fields of the first record matching the provided
* conditions and returns it as an associative array, or false when nothing matches.
* This method makes use of selectFields and expects the same parameters and
* returns the same results (if there are any, if there are none, this method returns false).
@@ -442,10 +442,11 @@ interface IORMTable {
* Takes an array of field names with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
- * @param array $fieldNames
+ * @param string[] $fieldNames
*
- * @return array
+ * @return string[]
*/
public function unprefixFieldNames( array $fieldNames );
@@ -453,6 +454,7 @@ interface IORMTable {
* Takes a field name with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
* @param string $fieldName
*
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 73456e23..4551e2d7 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -27,7 +27,7 @@
*/
abstract class LBFactory {
/** @var LBFactory */
- protected static $instance;
+ private static $instance;
/**
* Disables all access to the load balancer, will cause all database access
@@ -43,7 +43,7 @@ abstract class LBFactory {
*
* @return LBFactory
*/
- static function &singleton() {
+ public static function singleton() {
global $wgLBFactoryConf;
if ( is_null( self::$instance ) ) {
@@ -87,7 +87,7 @@ abstract class LBFactory {
/**
* Shut down, close connections and destroy the cached instance.
*/
- static function destroyInstance() {
+ public static function destroyInstance() {
if ( self::$instance ) {
self::$instance->shutdown();
self::$instance->forEachLBCallMethod( 'closeAll' );
@@ -100,7 +100,7 @@ abstract class LBFactory {
*
* @param LBFactory $instance
*/
- static function setInstance( $instance ) {
+ public static function setInstance( $instance ) {
self::destroyInstance();
self::$instance = $instance;
}
@@ -109,7 +109,7 @@ abstract class LBFactory {
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
* @param array $conf
*/
- abstract function __construct( $conf );
+ abstract public function __construct( array $conf );
/**
* Create a new load balancer object. The resulting object will be untracked,
@@ -118,7 +118,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function newMainLB( $wiki = false );
+ abstract public function newMainLB( $wiki = false );
/**
* Get a cached (tracked) load balancer object.
@@ -126,7 +126,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function getMainLB( $wiki = false );
+ abstract public function getMainLB( $wiki = false );
/**
* Create a new load balancer for external storage. The resulting object will be
@@ -137,7 +137,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function newExternalLB( $cluster, $wiki = false );
+ abstract protected function newExternalLB( $cluster, $wiki = false );
/**
* Get a cached (tracked) load balancer for external storage
@@ -146,7 +146,7 @@ abstract class LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- abstract function &getExternalLB( $cluster, $wiki = false );
+ abstract public function &getExternalLB( $cluster, $wiki = false );
/**
* Execute a function for each tracked load balancer
@@ -156,13 +156,13 @@ abstract class LBFactory {
* @param callable $callback
* @param array $params
*/
- abstract function forEachLB( $callback, $params = array() );
+ abstract public function forEachLB( $callback, array $params = array() );
/**
* Prepare all tracked load balancers for shutdown
* STUB
*/
- function shutdown() {
+ public function shutdown() {
}
/**
@@ -171,24 +171,16 @@ abstract class LBFactory {
* @param string $methodName
* @param array $args
*/
- function forEachLBCallMethod( $methodName, $args = array() ) {
- $this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
- }
-
- /**
- * Private helper for forEachLBCallMethod
- * @param LoadBalancer $loadBalancer
- * @param string $methodName
- * @param array $args
- */
- function callMethod( $loadBalancer, $methodName, $args ) {
- call_user_func_array( array( $loadBalancer, $methodName ), $args );
+ private function forEachLBCallMethod( $methodName, array $args = array() ) {
+ $this->forEachLB( function ( LoadBalancer $loadBalancer, $methodName, array $args ) {
+ call_user_func_array( array( $loadBalancer, $methodName ), $args );
+ }, array( $methodName, $args ) );
}
/**
* Commit changes on all master connections
*/
- function commitMasterChanges() {
+ public function commitMasterChanges() {
$this->forEachLBCallMethod( 'commitMasterChanges' );
}
@@ -196,7 +188,7 @@ abstract class LBFactory {
* Rollback changes on all master connections
* @since 1.23
*/
- function rollbackMasterChanges() {
+ public function rollbackMasterChanges() {
$this->forEachLBCallMethod( 'rollbackMasterChanges' );
}
@@ -205,7 +197,7 @@ abstract class LBFactory {
* @since 1.23
* @return bool
*/
- function hasMasterChanges() {
+ public function hasMasterChanges() {
$ret = false;
$this->forEachLB( function ( $lb ) use ( &$ret ) {
$ret = $ret || $lb->hasMasterChanges();
@@ -219,15 +211,15 @@ abstract class LBFactory {
*/
class LBFactorySimple extends LBFactory {
/** @var LoadBalancer */
- protected $mainLB;
+ private $mainLB;
/** @var LoadBalancer[] */
- protected $extLBs = array();
+ private $extLBs = array();
/** @var ChronologyProtector */
- protected $chronProt;
+ private $chronProt;
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
$this->chronProt = new ChronologyProtector;
}
@@ -235,7 +227,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
global $wgDBservers;
if ( $wgDBservers ) {
$servers = $wgDBservers;
@@ -274,7 +266,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
if ( !isset( $this->mainLB ) ) {
$this->mainLB = $this->newMainLB( $wiki );
$this->mainLB->parentInfo( array( 'id' => 'main' ) );
@@ -290,7 +282,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
global $wgExternalServers;
if ( !isset( $wgExternalServers[$cluster] ) ) {
throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
@@ -306,7 +298,7 @@ class LBFactorySimple extends LBFactory {
* @param bool|string $wiki
* @return array
*/
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
@@ -324,7 +316,7 @@ class LBFactorySimple extends LBFactory {
* @param callable $callback
* @param array $params
*/
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
if ( isset( $this->mainLB ) ) {
call_user_func_array( $callback, array_merge( array( $this->mainLB ), $params ) );
}
@@ -333,7 +325,7 @@ class LBFactorySimple extends LBFactory {
}
}
- function shutdown() {
+ public function shutdown() {
if ( $this->mainLB ) {
$this->chronProt->shutdownLB( $this->mainLB );
}
@@ -352,26 +344,26 @@ class LBFactorySimple extends LBFactory {
* LBFactory::enableBackend() to return to normal behavior
*/
class LBFactoryFake extends LBFactory {
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
}
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
throw new DBAccessError;
}
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
throw new DBAccessError;
}
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
throw new DBAccessError;
}
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
throw new DBAccessError;
}
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
}
}
@@ -379,7 +371,7 @@ class LBFactoryFake extends LBFactory {
* Exception class for attempted DB access
*/
class DBAccessError extends MWException {
- function __construct() {
+ public function __construct() {
parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
"This is not allowed." );
}
diff --git a/includes/db/LBFactoryMulti.php b/includes/db/LBFactoryMulti.php
index bac96523..aa305ab1 100644
--- a/includes/db/LBFactoryMulti.php
+++ b/includes/db/LBFactoryMulti.php
@@ -77,82 +77,82 @@ class LBFactoryMulti extends LBFactory {
// Required settings
/** @var array A map of database names to section names */
- protected $sectionsByDB;
+ private $sectionsByDB;
/**
* @var array A 2-d map. For each section, gives a map of server names to
* load ratios
*/
- protected $sectionLoads;
+ private $sectionLoads;
/**
* @var array A server info associative array as documented for
* $wgDBservers. The host, hostName and load entries will be
* overridden
*/
- protected $serverTemplate;
+ private $serverTemplate;
// Optional settings
/** @var array A 3-d map giving server load ratios for each section and group */
- protected $groupLoadsBySection = array();
+ private $groupLoadsBySection = array();
/** @var array A 3-d map giving server load ratios by DB name */
- protected $groupLoadsByDB = array();
+ private $groupLoadsByDB = array();
/** @var array A map of hostname to IP address */
- protected $hostsByName = array();
+ private $hostsByName = array();
/** @var array A map of external storage cluster name to server load map */
- protected $externalLoads = array();
+ private $externalLoads = array();
/**
* @var array A set of server info keys overriding serverTemplate for
* external storage
*/
- protected $externalTemplateOverrides;
+ private $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;
+ private $templateOverridesByServer;
/** @var array A 2-d map overriding the server info by external storage cluster */
- protected $templateOverridesByCluster;
+ private $templateOverridesByCluster;
/** @var array An override array for all master servers */
- protected $masterTemplateOverrides;
+ private $masterTemplateOverrides;
/**
* @var array|bool A map of section name to read-only message. Missing or
* false for read/write
*/
- protected $readOnlyBySection = array();
+ private $readOnlyBySection = array();
// Other stuff
/** @var array Load balancer factory configuration */
- protected $conf;
+ private $conf;
/** @var LoadBalancer[] */
- protected $mainLBs = array();
+ private $mainLBs = array();
/** @var LoadBalancer[] */
- protected $extLBs = array();
+ private $extLBs = array();
/** @var string */
- protected $lastWiki;
+ private $lastWiki;
/** @var string */
- protected $lastSection;
+ private $lastSection;
/**
* @param array $conf
* @throws MWException
*/
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
$this->chronProt = new ChronologyProtector;
$this->conf = $conf;
$required = array( 'sectionsByDB', 'sectionLoads', 'serverTemplate' );
@@ -186,7 +186,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return string
*/
- function getSectionForWiki( $wiki = false ) {
+ private function getSectionForWiki( $wiki = false ) {
if ( $this->lastWiki === $wiki ) {
return $this->lastSection;
}
@@ -206,7 +206,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
$section = $this->getSectionForWiki( $wiki );
$groupLoads = array();
@@ -229,7 +229,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancer
*/
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
$section = $this->getSectionForWiki( $wiki );
if ( !isset( $this->mainLBs[$section] ) ) {
$lb = $this->newMainLB( $wiki, $section );
@@ -247,7 +247,7 @@ class LBFactoryMulti extends LBFactory {
* @throws MWException
* @return LoadBalancer
*/
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->externalLoads[$cluster] ) ) {
throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
@@ -267,7 +267,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancer
*/
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
$this->extLBs[$cluster]->parentInfo( array( 'id' => "ext-$cluster" ) );
@@ -285,7 +285,7 @@ class LBFactoryMulti extends LBFactory {
* @param array $groupLoads
* @return LoadBalancer
*/
- function newLoadBalancer( $template, $loads, $groupLoads ) {
+ private function newLoadBalancer( $template, $loads, $groupLoads ) {
$servers = $this->makeServerArray( $template, $loads, $groupLoads );
$lb = new LoadBalancer( array(
'servers' => $servers,
@@ -302,7 +302,7 @@ class LBFactoryMulti extends LBFactory {
* @param array $groupLoads
* @return array
*/
- function makeServerArray( $template, $loads, $groupLoads ) {
+ private function makeServerArray( $template, $loads, $groupLoads ) {
$servers = array();
$master = true;
$groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
@@ -344,7 +344,7 @@ class LBFactoryMulti extends LBFactory {
* @param array $groupLoads
* @return array
*/
- function reindexGroupLoads( $groupLoads ) {
+ private function reindexGroupLoads( $groupLoads ) {
$reindexed = array();
foreach ( $groupLoads as $group => $loads ) {
foreach ( $loads as $server => $load ) {
@@ -360,7 +360,7 @@ class LBFactoryMulti extends LBFactory {
* @param bool|string $wiki
* @return array
*/
- function getDBNameAndPrefix( $wiki = false ) {
+ private function getDBNameAndPrefix( $wiki = false ) {
if ( $wiki === false ) {
global $wgDBname, $wgDBprefix;
@@ -377,7 +377,7 @@ class LBFactoryMulti extends LBFactory {
* @param callable $callback
* @param array $params
*/
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
foreach ( $this->mainLBs as $lb ) {
call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
}
@@ -386,7 +386,7 @@ class LBFactoryMulti extends LBFactory {
}
}
- function shutdown() {
+ public function shutdown() {
foreach ( $this->mainLBs as $lb ) {
$this->chronProt->shutdownLB( $lb );
}
diff --git a/includes/db/LBFactorySingle.php b/includes/db/LBFactorySingle.php
index 3a4d829b..a41dadfa 100644
--- a/includes/db/LBFactorySingle.php
+++ b/includes/db/LBFactorySingle.php
@@ -26,13 +26,13 @@
*/
class LBFactorySingle extends LBFactory {
/** @var LoadBalancerSingle */
- protected $lb;
+ private $lb;
/**
* @param array $conf An associative array with one member:
* - connection: The DatabaseBase connection object
*/
- function __construct( $conf ) {
+ public function __construct( array $conf ) {
$this->lb = new LoadBalancerSingle( $conf );
}
@@ -40,7 +40,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancerSingle
*/
- function newMainLB( $wiki = false ) {
+ public function newMainLB( $wiki = false ) {
return $this->lb;
}
@@ -48,7 +48,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki
* @return LoadBalancerSingle
*/
- function getMainLB( $wiki = false ) {
+ public function getMainLB( $wiki = false ) {
return $this->lb;
}
@@ -57,7 +57,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancerSingle
*/
- function newExternalLB( $cluster, $wiki = false ) {
+ protected function newExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
@@ -66,7 +66,7 @@ class LBFactorySingle extends LBFactory {
* @param bool|string $wiki Wiki ID, or false for the current wiki
* @return LoadBalancerSingle
*/
- function &getExternalLB( $cluster, $wiki = false ) {
+ public function &getExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
@@ -74,7 +74,7 @@ class LBFactorySingle extends LBFactory {
* @param string|callable $callback
* @param array $params
*/
- function forEachLB( $callback, $params = array() ) {
+ public function forEachLB( $callback, array $params = array() ) {
call_user_func_array( $callback, array_merge( array( $this->lb ), $params ) );
}
}
@@ -84,12 +84,12 @@ class LBFactorySingle extends LBFactory {
*/
class LoadBalancerSingle extends LoadBalancer {
/** @var DatabaseBase */
- protected $db;
+ private $db;
/**
* @param array $params
*/
- function __construct( $params ) {
+ public function __construct( array $params ) {
$this->db = $params['connection'];
parent::__construct( array( 'servers' => array( array(
'type' => $this->db->getType(),
@@ -106,7 +106,7 @@ class LoadBalancerSingle extends LoadBalancer {
*
* @return DatabaseBase
*/
- function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
return $this->db;
}
}
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index e517a025..d9584e14 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -28,13 +28,13 @@
* @ingroup Database
*/
class LoadBalancer {
- /** @var array Map of (server index => server config array) */
+ /** @var array[] Map of (server index => server config array) */
private $mServers;
- /** @var array Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
+ /** @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) */
+ /** @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;
@@ -58,8 +58,13 @@ class LoadBalancer {
private $mLaggedSlaveMode;
/** @var string The last DB selection or connection error */
private $mLastError = 'Unknown error';
- /** @var array Process cache of LoadMonitor::getLagTimes() */
- private $mLagTimes;
+ /** @var integer Total connections opened */
+ private $connsOpened = 0;
+ /** @var ProcessCacheLRU */
+ private $mProcCache;
+
+ /** @var integer Warn when this many connection are held */
+ const CONN_HELD_WARN_THRESHOLD = 10;
/**
* @param array $params Array with keys:
@@ -67,7 +72,7 @@ class LoadBalancer {
* loadMonitor Name of a class used to fetch server lag and load.
* @throws MWException
*/
- function __construct( $params ) {
+ public function __construct( array $params ) {
if ( !isset( $params['servers'] ) ) {
throw new MWException( __CLASS__ . ': missing servers parameter' );
}
@@ -108,6 +113,8 @@ class LoadBalancer {
}
}
}
+
+ $this->mProcCache = new ProcessCacheLRU( 30 );
}
/**
@@ -115,7 +122,7 @@ class LoadBalancer {
*
* @return LoadMonitor
*/
- function getLoadMonitor() {
+ private function getLoadMonitor() {
if ( !isset( $this->mLoadMonitor ) ) {
$class = $this->mLoadMonitorClass;
$this->mLoadMonitor = new $class( $this );
@@ -129,7 +136,7 @@ class LoadBalancer {
* @param mixed $x
* @return mixed
*/
- function parentInfo( $x = null ) {
+ public function parentInfo( $x = null ) {
return wfSetVar( $this->mParentInfo, $x );
}
@@ -142,24 +149,30 @@ class LoadBalancer {
* @param array $weights
* @return bool|int|string
*/
- function pickRandom( $weights ) {
+ public function pickRandom( array $weights ) {
return ArrayUtils::pickRandom( $weights );
}
/**
* @param array $loads
* @param bool|string $wiki Wiki to get non-lagged for
+ * @param float $maxLag Restrict the maximum allowed lag to this many seconds
* @return bool|int|string
*/
- function getRandomNonLagged( $loads, $wiki = false ) {
- # Unset excessively lagged servers
+ private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
$lags = $this->getLagTimes( $wiki );
+
+ # Unset excessively lagged servers
foreach ( $lags as $i => $lag ) {
if ( $i != 0 ) {
+ $maxServerLag = $maxLag;
+ if ( isset( $this->mServers[$i]['max lag'] ) ) {
+ $maxServerLag = min( $maxServerLag, $this->mServers[$i]['max lag'] );
+ }
if ( $lag === false ) {
wfDebugLog( 'replication', "Server #$i is not replicating" );
unset( $loads[$i] );
- } elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
+ } elseif ( $lag > $maxServerLag ) {
wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)" );
unset( $loads[$i] );
}
@@ -195,12 +208,12 @@ class LoadBalancer {
* always return a consistent index during a given invocation
*
* Side effect: opens connections to databases
- * @param bool|string $group
- * @param bool|string $wiki
+ * @param string|bool $group Query group, or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @throws MWException
* @return bool|int|string
*/
- function getReaderIndex( $group = false, $wiki = false ) {
+ public function getReaderIndex( $group = false, $wiki = false ) {
global $wgReadOnly, $wgDBtype;
# @todo FIXME: For now, only go through all this for mysql databases
@@ -216,8 +229,6 @@ class LoadBalancer {
return $this->mReadIndex;
}
- $section = new ProfileSection( __METHOD__ );
-
# Find the relevant load array
if ( $group !== false ) {
if ( isset( $this->mGroupLoads[$group] ) ) {
@@ -250,7 +261,19 @@ class LoadBalancer {
if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
$i = ArrayUtils::pickRandom( $currentLoads );
} else {
- $i = $this->getRandomNonLagged( $currentLoads, $wiki );
+ $i = false;
+ if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
+ # ChronologyProtecter causes mWaitForPos to be set via sessions.
+ # This triggers doWait() after connect, so it's especially good to
+ # avoid lagged servers so as to avoid just blocking in that method.
+ $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
+ # Aim for <= 1 second of waiting (being too picky can backfire)
+ $i = $this->getRandomNonLagged( $currentLoads, $wiki, $ago + 1 );
+ }
+ if ( $i === false ) {
+ # Any server with less lag than it's 'max lag' param is preferable
+ $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" );
@@ -305,36 +328,23 @@ class LoadBalancer {
$this->mServers[$i]['slave pos'] = $conn->getSlavePos();
}
}
- if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group !== false ) {
+ if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
$this->mReadIndex = $i;
}
+ $serverName = $this->getServerName( $i );
+ wfDebug( __METHOD__ . ": using server $serverName for group '$group'\n" );
}
return $i;
}
/**
- * Wait for a specified number of microseconds, and return the period waited
- * @param int $t
- * @return int
- */
- function sleep( $t ) {
- wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . ": waiting $t us\n" );
- usleep( $t );
- wfProfileOut( __METHOD__ );
-
- return $t;
- }
-
- /**
* 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 DBMasterPos $pos
*/
public function waitFor( $pos ) {
- wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
$i = $this->mReadIndex;
@@ -344,7 +354,6 @@ class LoadBalancer {
$this->mLaggedSlaveMode = true;
}
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -354,7 +363,6 @@ class LoadBalancer {
* @return bool Success (able to connect and no timeouts reached)
*/
public function waitForAll( $pos, $timeout = null ) {
- wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
$serverCount = count( $this->mServers );
@@ -364,7 +372,6 @@ class LoadBalancer {
$ok = $this->doWait( $i, true, $timeout ) && $ok;
}
}
- wfProfileOut( __METHOD__ );
return $ok;
}
@@ -376,7 +383,7 @@ class LoadBalancer {
* @param int $i
* @return DatabaseBase|bool False on failure
*/
- function getAnyOpenConnection( $i ) {
+ public function getAnyOpenConnection( $i ) {
foreach ( $this->mConns as $conns ) {
if ( !empty( $conns[$i] ) ) {
return reset( $conns[$i] );
@@ -394,7 +401,9 @@ class LoadBalancer {
* @return bool
*/
protected function doWait( $index, $open = false, $timeout = null ) {
- # Find a connection to wait on
+ $close = false; // close the connection afterwards
+
+ # Find a connection to wait on, creating one if needed and allowed
$conn = $this->getAnyOpenConnection( $index );
if ( !$conn ) {
if ( !$open ) {
@@ -408,6 +417,9 @@ class LoadBalancer {
return false;
}
+ // Avoid connection spam in waitForAll() when connections
+ // are made just for the sake of doing this lag check.
+ $close = true;
}
}
@@ -417,14 +429,21 @@ class LoadBalancer {
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;
+ $server = $this->mServers[$index]['host'];
+ $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
+ wfDebug( "$msg\n" );
+ wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+ $ok = false;
} else {
wfDebug( __METHOD__ . ": Done\n" );
+ $ok = true;
+ }
- return true;
+ if ( $close ) {
+ $this->closeConnection( $conn );
}
+
+ return $ok;
}
/**
@@ -432,17 +451,14 @@ class LoadBalancer {
* This is the main entry point for this class.
*
* @param int $i Server index
- * @param array $groups Query groups
- * @param bool|string $wiki Wiki ID
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
*
* @throws MWException
* @return DatabaseBase
*/
- public function &getConnection( $i, $groups = array(), $wiki = false ) {
- wfProfileIn( __METHOD__ );
-
+ public function getConnection( $i, $groups = array(), $wiki = false ) {
if ( $i === null || $i === false ) {
- wfProfileOut( __METHOD__ );
throw new MWException( 'Attempt to call ' . __METHOD__ .
' with invalid server index' );
}
@@ -451,22 +467,20 @@ class LoadBalancer {
$wiki = false;
}
- # Query groups
+ $groups = ( $groups === false || $groups === array() )
+ ? array( false ) // check one "group": the generic pool
+ : (array)$groups;
+
+ $masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
+ $oldConnsOpened = $this->connsOpened; // connections open now
+
if ( $i == DB_MASTER ) {
$i = $this->getWriterIndex();
- } elseif ( !is_array( $groups ) ) {
- $groupIndex = $this->getReaderIndex( $groups, $wiki );
- if ( $groupIndex !== false ) {
- $serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__ . ": using server $serverName for group $groups\n" );
- $i = $groupIndex;
- }
} else {
+ # Try to find an available server in any the query groups (in order)
foreach ( $groups as $group ) {
$groupIndex = $this->getReaderIndex( $group, $wiki );
if ( $groupIndex !== false ) {
- $serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__ . ": using server $serverName for group $group\n" );
$i = $groupIndex;
break;
}
@@ -476,11 +490,13 @@ class LoadBalancer {
# Operation-based index
if ( $i == DB_SLAVE ) {
$this->mLastError = 'Unknown error'; // reset error string
- $i = $this->getReaderIndex( false, $wiki );
+ # Try the general server pool if $groups are unavailable.
+ $i = in_array( false, $groups, true )
+ ? false // don't bother with this if that is what was tried above
+ : $this->getReaderIndex( false, $wiki );
# Couldn't find a working server in getReaderIndex()?
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
- wfProfileOut( __METHOD__ );
return $this->reportConnectionError();
}
@@ -489,12 +505,17 @@ class LoadBalancer {
# Now we have an explicit index into the servers array
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return $this->reportConnectionError();
}
- wfProfileOut( __METHOD__ );
+ # Profile any new connections that happen
+ if ( $this->connsOpened > $oldConnsOpened ) {
+ $host = $conn->getServer();
+ $dbname = $conn->getDBname();
+ $trxProf = Profiler::instance()->getTransactionProfiler();
+ $trxProf->recordConnection( $host, $dbname, $masterOnly );
+ }
return $conn;
}
@@ -556,8 +577,8 @@ class LoadBalancer {
* @see LoadBalancer::getConnection() for parameter information
*
* @param int $db
- * @param mixed $groups
- * @param bool|string $wiki
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @return DBConnRef
*/
public function getConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -572,8 +593,8 @@ class LoadBalancer {
* @see LoadBalancer::getConnection() for parameter information
*
* @param int $db
- * @param mixed $groups
- * @param bool|string $wiki
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @return DBConnRef
*/
public function getLazyConnectionRef( $db, $groups = array(), $wiki = false ) {
@@ -589,16 +610,14 @@ class LoadBalancer {
* error will be available via $this->mErrorConnection.
*
* @param int $i Server index
- * @param bool|string $wiki Wiki ID to open
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
* @return DatabaseBase
*
* @access private
*/
- function openConnection( $i, $wiki = false ) {
- wfProfileIn( __METHOD__ );
+ public function openConnection( $i, $wiki = false ) {
if ( $wiki !== false ) {
$conn = $this->openForeignConnection( $i, $wiki );
- wfProfileOut( __METHOD__ );
return $conn;
}
@@ -617,7 +636,6 @@ class LoadBalancer {
$conn = false;
}
}
- wfProfileOut( __METHOD__ );
return $conn;
}
@@ -640,8 +658,7 @@ class LoadBalancer {
* @param string $wiki Wiki ID to open
* @return DatabaseBase
*/
- function openForeignConnection( $i, $wiki ) {
- wfProfileIn( __METHOD__ );
+ private function openForeignConnection( $i, $wiki ) {
list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
// Reuse an already-used connection
@@ -658,7 +675,9 @@ class LoadBalancer {
$conn = reset( $this->mConns['foreignFree'][$i] );
$oldWiki = key( $this->mConns['foreignFree'][$i] );
- if ( !$conn->selectDB( $dbName ) ) {
+ // The empty string as a DB name means "don't care".
+ // DatabaseMysqlBase::open() already handle this on connection.
+ if ( $dbName !== '' && !$conn->selectDB( $dbName ) ) {
$this->mLastError = "Error selecting database $dbName on server " .
$conn->getServer() . " from client host " . wfHostname() . "\n";
$this->mErrorConnection = $conn;
@@ -692,7 +711,6 @@ class LoadBalancer {
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
- wfProfileOut( __METHOD__ );
return $conn;
}
@@ -704,7 +722,7 @@ class LoadBalancer {
* @access private
* @return bool
*/
- function isOpen( $index ) {
+ private function isOpen( $index ) {
if ( !is_integer( $index ) ) {
return false;
}
@@ -722,7 +740,7 @@ class LoadBalancer {
* @throws MWException
* @return DatabaseBase
*/
- function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
if ( !is_array( $server ) ) {
throw new MWException( 'You must update your load-balancing configuration. ' .
'See DefaultSettings.php entry for $wgDBservers.' );
@@ -732,6 +750,14 @@ class LoadBalancer {
$server['dbname'] = $dbNameOverride;
}
+ // Log when many connection are made on requests
+ if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
+ $masterAddr = $this->getServerName( 0 );
+ wfDebugLog( 'DBPerformance', __METHOD__ . ": " .
+ "{$this->connsOpened}+ connections made (master=$masterAddr)\n" .
+ wfBacktrace( true ) );
+ }
+
# Create object
try {
$db = DatabaseBase::factory( $server['type'], $server );
@@ -758,17 +784,27 @@ class LoadBalancer {
*/
private function reportConnectionError() {
$conn = $this->mErrorConnection; // The connection which caused the error
+ $context = array(
+ 'method' => __METHOD__,
+ 'last_error' => $this->mLastError,
+ );
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}" );
+ wfLogDBError(
+ "LB failure with no last connection. Connection error: {last_error}",
+ $context
+ );
// 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})" );
- $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); // throws DBConnectionError
+ $context['db_server'] = $conn->getProperty( 'mServer' );
+ wfLogDBError(
+ "Connection error: {last_error} ({db_server})",
+ $context
+ );
+ $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" ); // throws DBConnectionError
}
return false; /* not reached */
@@ -777,7 +813,7 @@ class LoadBalancer {
/**
* @return int
*/
- function getWriterIndex() {
+ private function getWriterIndex() {
return 0;
}
@@ -787,7 +823,7 @@ class LoadBalancer {
* @param string $i
* @return bool
*/
- function haveIndex( $i ) {
+ public function haveIndex( $i ) {
return array_key_exists( $i, $this->mServers );
}
@@ -797,7 +833,7 @@ class LoadBalancer {
* @param string $i
* @return bool
*/
- function isNonZeroLoad( $i ) {
+ public function isNonZeroLoad( $i ) {
return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
}
@@ -806,7 +842,7 @@ class LoadBalancer {
*
* @return int
*/
- function getServerCount() {
+ public function getServerCount() {
return count( $this->mServers );
}
@@ -816,7 +852,7 @@ class LoadBalancer {
* @param string $i
* @return string
*/
- function getServerName( $i ) {
+ public function getServerName( $i ) {
if ( isset( $this->mServers[$i]['hostName'] ) ) {
return $this->mServers[$i]['hostName'];
} elseif ( isset( $this->mServers[$i]['host'] ) ) {
@@ -831,7 +867,7 @@ class LoadBalancer {
* @param int $i
* @return array|bool
*/
- function getServerInfo( $i ) {
+ public function getServerInfo( $i ) {
if ( isset( $this->mServers[$i] ) ) {
return $this->mServers[$i];
} else {
@@ -845,7 +881,7 @@ class LoadBalancer {
* @param int $i
* @param array $serverInfo
*/
- function setServerInfo( $i, $serverInfo ) {
+ public function setServerInfo( $i, array $serverInfo ) {
$this->mServers[$i] = $serverInfo;
}
@@ -853,7 +889,7 @@ class LoadBalancer {
* Get the current master position for chronology control purposes
* @return mixed
*/
- function getMasterPos() {
+ public function getMasterPos() {
# If this entire request was served from a slave without opening a connection to the
# master (however unlikely that may be), then we can fetch the position from the slave.
$masterConn = $this->getAnyOpenConnection( 0 );
@@ -879,7 +915,7 @@ class LoadBalancer {
/**
* Close all open connections
*/
- function closeAll() {
+ public function closeAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
/** @var DatabaseBase $conn */
@@ -893,6 +929,7 @@ class LoadBalancer {
'foreignFree' => array(),
'foreignUsed' => array(),
);
+ $this->connsOpened = 0;
}
/**
@@ -901,7 +938,7 @@ class LoadBalancer {
* If you use $conn->close() directly, the load balancer won't update its state.
* @param DatabaseBase $conn
*/
- function closeConnection( $conn ) {
+ public function closeConnection( $conn ) {
$done = false;
foreach ( $this->mConns as $i1 => $conns2 ) {
foreach ( $conns2 as $i2 => $conns3 ) {
@@ -909,6 +946,7 @@ class LoadBalancer {
if ( $conn === $candidateConn ) {
$conn->close();
unset( $this->mConns[$i1][$i2][$i3] );
+ --$this->connsOpened;
$done = true;
break;
}
@@ -923,7 +961,7 @@ class LoadBalancer {
/**
* Commit transactions on all open connections
*/
- function commitAll() {
+ public function commitAll() {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
/** @var DatabaseBase[] $conns3 */
@@ -939,8 +977,7 @@ class LoadBalancer {
/**
* Issue COMMIT only on master, only if queries were done on connection
*/
- function commitMasterChanges() {
- // Always 0, but who knows.. :)
+ public function commitMasterChanges() {
$masterIndex = $this->getWriterIndex();
foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
@@ -959,8 +996,9 @@ class LoadBalancer {
* Issue ROLLBACK only on master, only if queries were done on connection
* @since 1.23
*/
- function rollbackMasterChanges() {
- // Always 0, but who knows.. :)
+ public function rollbackMasterChanges() {
+ $failedServers = array();
+
$masterIndex = $this->getWriterIndex();
foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
@@ -969,28 +1007,36 @@ class LoadBalancer {
/** @var DatabaseBase $conn */
foreach ( $conns2[$masterIndex] as $conn ) {
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
- $conn->rollback( __METHOD__, 'flush' );
+ try {
+ $conn->rollback( __METHOD__, 'flush' );
+ } catch ( DBError $e ) {
+ MWExceptionHandler::logException( $e );
+ $failedServers[] = $conn->getServer();
+ }
}
}
}
+
+ if ( $failedServers ) {
+ throw new DBExpectedError( null, "Rollback failed on server(s) " .
+ implode( ', ', array_unique( $failedServers ) ) );
+ }
}
/**
* @return bool Whether a master connection is already open
* @since 1.24
*/
- function hasMasterConnection() {
+ public function hasMasterConnection() {
return $this->isOpen( $this->getWriterIndex() );
}
/**
- * Determine if there are any pending changes that need to be rolled back
- * or committed.
+ * Determine if there are pending changes in a transaction by this thread
* @since 1.23
* @return bool
*/
- function hasMasterChanges() {
- // Always 0, but who knows.. :)
+ public function hasMasterChanges() {
$masterIndex = $this->getWriterIndex();
foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
@@ -1007,17 +1053,52 @@ class LoadBalancer {
}
/**
+ * Get the timestamp of the latest write query done by this thread
+ * @since 1.25
+ * @return float|bool UNIX timestamp or false
+ */
+ public function lastMasterChangeTimestamp() {
+ $lastTime = false;
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $conns2 ) {
+ if ( empty( $conns2[$masterIndex] ) ) {
+ continue;
+ }
+ /** @var DatabaseBase $conn */
+ foreach ( $conns2[$masterIndex] as $conn ) {
+ $lastTime = max( $lastTime, $conn->lastDoneWrites() );
+ }
+ }
+ return $lastTime;
+ }
+
+ /**
+ * Check if this load balancer object had any recent or still
+ * pending writes issued against it by this PHP thread
+ *
+ * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
+ * @return bool
+ * @since 1.25
+ */
+ public function hasOrMadeRecentMasterChanges( $age = null ) {
+ $age = ( $age === null ) ? $this->mWaitTimeout : $age;
+
+ return ( $this->hasMasterChanges()
+ || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
+ }
+
+ /**
* @param mixed $value
* @return mixed
*/
- function waitTimeout( $value = null ) {
+ public function waitTimeout( $value = null ) {
return wfSetVar( $this->mWaitTimeout, $value );
}
/**
* @return bool
*/
- function getLaggedSlaveMode() {
+ public function getLaggedSlaveMode() {
return $this->mLaggedSlaveMode;
}
@@ -1026,7 +1107,7 @@ class LoadBalancer {
* @param null|bool $mode
* @return bool
*/
- function allowLagged( $mode = null ) {
+ public function allowLagged( $mode = null ) {
if ( $mode === null ) {
return $this->mAllowLagged;
}
@@ -1038,7 +1119,7 @@ class LoadBalancer {
/**
* @return bool
*/
- function pingAll() {
+ public function pingAll() {
$success = true;
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
@@ -1059,7 +1140,7 @@ class LoadBalancer {
* @param callable $callback
* @param array $params
*/
- function forEachOpenConnection( $callback, $params = array() ) {
+ public function forEachOpenConnection( $callback, array $params = array() ) {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
@@ -1071,7 +1152,8 @@ class LoadBalancer {
}
/**
- * Get the hostname and lag time of the most-lagged slave.
+ * Get the hostname and lag time of the most-lagged slave
+ *
* This is useful for maintenance scripts that need to throttle their updates.
* May attempt to open connections to slaves on the default DB. If there is
* no lag, the maximum lag will be reported as -1.
@@ -1079,72 +1161,50 @@ class LoadBalancer {
* @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 ) {
+ public function getMaxLag( $wiki = false ) {
$maxLag = -1;
$host = '';
$maxIndex = 0;
- if ( $this->getServerCount() <= 1 ) { // no replication = no lag
- return array( $host, $maxLag, $maxIndex );
+ if ( $this->getServerCount() <= 1 ) {
+ return array( $host, $maxLag, $maxIndex ); // no replication = no lag
}
- // 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 );
- }
- if ( !$conn ) {
- $conn = $this->openConnection( $i, $wiki );
- }
- if ( !$conn ) {
- continue;
- }
- $lag = $conn->getLag();
- if ( $lag > $maxLag ) {
- $maxLag = $lag;
- $host = $this->mServers[$i]['host'];
- $maxIndex = $i;
- }
+ $lagTimes = $this->getLagTimes( $wiki );
+ foreach ( $lagTimes as $i => $lag ) {
+ if ( $lag > $maxLag ) {
+ $maxLag = $lag;
+ $host = $this->mServers[$i]['host'];
+ $maxIndex = $i;
}
- $maxLagInfo = array( $host, $maxLag, $maxIndex );
- $cache->set( $key, $maxLagInfo, 5 );
}
- return $maxLagInfo;
+ return array( $host, $maxLag, $maxIndex );
}
/**
* Get lag time for each server
- * Results are cached for a short time in memcached, and indefinitely in the process cache
+ *
+ * Results are cached for a short time in memcached/process cache
*
* @param string|bool $wiki
- * @return array
+ * @return int[] Map of (server index => seconds)
*/
- function getLagTimes( $wiki = false ) {
- # Try process cache
- if ( isset( $this->mLagTimes ) ) {
- return $this->mLagTimes;
+ public function getLagTimes( $wiki = false ) {
+ if ( $this->getServerCount() <= 1 ) {
+ return array( 0 => 0 ); // no replication = no lag
}
- if ( $this->getServerCount() == 1 ) {
- # No replication
- $this->mLagTimes = array( 0 => 0 );
- } else {
- # Send the request to the load monitor
- $this->mLagTimes = $this->getLoadMonitor()->getLagTimes(
- array_keys( $this->mServers ), $wiki );
+
+ if ( $this->mProcCache->has( 'slave_lag', 'times', 1 ) ) {
+ return $this->mProcCache->get( 'slave_lag', 'times' );
}
- return $this->mLagTimes;
+ # Send the request to the load monitor
+ $times = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
+
+ $this->mProcCache->set( 'slave_lag', 'times', $times );
+
+ return $times;
}
/**
@@ -1161,7 +1221,7 @@ class LoadBalancer {
* @param DatabaseBase $conn
* @return int
*/
- function safeGetLag( $conn ) {
+ public function safeGetLag( $conn ) {
if ( $this->getServerCount() == 1 ) {
return 0;
} else {
@@ -1170,15 +1230,15 @@ class LoadBalancer {
}
/**
- * Clear the cache for getLagTimes
+ * Clear the cache for slag lag delay times
*/
- function clearLagTimeCache() {
- $this->mLagTimes = null;
+ public function clearLagTimeCache() {
+ $this->mProcCache->clear( 'slave_lag' );
}
}
/**
- * Helper class to handle automatically marking connectons as reusable (via RAII pattern)
+ * Helper class to handle automatically marking connections as reusable (via RAII pattern)
* as well handling deferring the actual network connection until the handle is used
*
* @ingroup Database
@@ -1186,13 +1246,13 @@ class LoadBalancer {
*/
class DBConnRef implements IDatabase {
/** @var LoadBalancer */
- protected $lb;
+ private $lb;
/** @var DatabaseBase|null */
- protected $conn;
+ private $conn;
/** @var array|null */
- protected $params;
+ private $params;
/**
* @param LoadBalancer $lb
@@ -1216,7 +1276,7 @@ class DBConnRef implements IDatabase {
return call_user_func_array( array( $this->conn, $name ), $arguments );
}
- function __destruct() {
+ public function __destruct() {
if ( $this->conn !== null ) {
$this->lb->reuseConnection( $this->conn );
}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 7281485b..91840dd9 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -48,7 +48,7 @@ interface LoadMonitor {
* @param array $serverIndexes
* @param string $wiki
*
- * @return array
+ * @return array Map of (server index => seconds)
*/
public function getLagTimes( $serverIndexes, $wiki );
}
@@ -93,8 +93,6 @@ class LoadMonitorMySQL implements LoadMonitor {
return array( 0 => 0 );
}
- $section = new ProfileSection( __METHOD__ );
-
$expiry = 5;
$requestRate = 10;
diff --git a/includes/db/ORMTable.php b/includes/db/ORMTable.php
index 2f898b75..562a8106 100644
--- a/includes/db/ORMTable.php
+++ b/includes/db/ORMTable.php
@@ -129,6 +129,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
* Gets the db field prefix.
*
* @since 1.20
+ * @deprecated since 1.25, use the $this->fieldPrefix property instead
*
* @return string
*/
@@ -189,7 +190,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @since 1.20
@@ -210,7 +211,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as DBDataObject. Field names get prefixed.
*
* @since 1.20
@@ -247,8 +248,8 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @param array $options
* @param null|string $functionName
* @return ResultWrapper
- * @throws DBQueryError If the query failed (even if the database was in
- * ignoreErrors mode).
+ * @throws Exception
+ * @throws MWException
*/
public function rawSelect( $fields = null, array $conditions = array(),
array $options = array(), $functionName = null
@@ -295,7 +296,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions and returns them as associative arrays.
* Provided field names get prefixed.
* Returned field names will not have a prefix.
@@ -345,7 +346,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the first matching record.
+ * Selects the specified fields of the first matching record.
* Field names get prefixed.
*
* @since 1.20
@@ -368,7 +369,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the records matching the provided
+ * Selects the specified fields of the records matching the provided
* conditions. Field names do NOT get prefixed.
*
* @since 1.20
@@ -399,7 +400,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
}
/**
- * Selects the the specified fields of the first record matching the provided
+ * Selects the specified fields of the first record matching the provided
* conditions and returns it as an associative array, or false when nothing matches.
* This method makes use of selectFields and expects the same parameters and
* returns the same results (if there are any, if there are none, this method returns false).
@@ -770,33 +771,54 @@ class ORMTable extends DBAccessBase implements IORMTable {
* @return string
*/
public function getPrefixedField( $field ) {
- return $this->getFieldPrefix() . $field;
+ return $this->fieldPrefix . $field;
}
/**
* Takes an array of field names with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
- * @param array $fieldNames
+ * @param string[] $fieldNames
*
- * @return array
+ * @return string[]
*/
public function unprefixFieldNames( array $fieldNames ) {
- return array_map( array( $this, 'unprefixFieldName' ), $fieldNames );
+ wfDeprecated( __METHOD__, '1.25' );
+
+ return $this->stripFieldPrefix( $fieldNames );
+ }
+
+ /**
+ * Takes an array of field names with prefix and returns the unprefixed equivalent.
+ *
+ * @param string[] $fieldNames
+ *
+ * @return string[]
+ */
+ private function stripFieldPrefix( array $fieldNames ) {
+ $start = strlen( $this->fieldPrefix );
+
+ return array_map( function ( $fieldName ) use ( $start ) {
+ return substr( $fieldName, $start );
+ }, $fieldNames );
}
/**
* Takes a field name with prefix and returns the unprefixed equivalent.
*
* @since 1.20
+ * @deprecated since 1.25, will be removed
*
* @param string $fieldName
*
* @return string
*/
public function unprefixFieldName( $fieldName ) {
- return substr( $fieldName, strlen( $this->getFieldPrefix() ) );
+ wfDeprecated( __METHOD__, '1.25' );
+
+ return substr( $fieldName, strlen( $this->fieldPrefix ) );
}
/**
@@ -832,7 +854,7 @@ class ORMTable extends DBAccessBase implements IORMTable {
$result = (array)$result;
$rawFields = array_combine(
- $this->unprefixFieldNames( array_keys( $result ) ),
+ $this->stripFieldPrefix( array_keys( $result ) ),
array_values( $result )
);
diff --git a/includes/debug/MWDebug.php b/includes/debug/MWDebug.php
index c2f22233..ae2d9954 100644
--- a/includes/debug/MWDebug.php
+++ b/includes/debug/MWDebug.php
@@ -26,8 +26,6 @@
* By default, most methods do nothing ( self::$enabled = false ). You have
* to explicitly call MWDebug::init() to enabled them.
*
- * @todo Profiler support
- *
* @since 1.19
*/
class MWDebug {
@@ -46,7 +44,7 @@ class MWDebug {
protected static $debug = array();
/**
- * SQL statements of the databses queries.
+ * SQL statements of the database queries.
*
* @var array $query
*/
@@ -311,12 +309,25 @@ class MWDebug {
*
* @since 1.19
* @param string $str
+ * @param array $context
*/
- public static function debugMsg( $str ) {
+ public static function debugMsg( $str, $context = array() ) {
global $wgDebugComments, $wgShowDebug;
if ( self::$enabled || $wgDebugComments || $wgShowDebug ) {
- self::$debug[] = rtrim( UtfNormal::cleanUp( $str ) );
+ if ( $context ) {
+ $prefix = '';
+ if ( isset( $context['prefix'] ) ) {
+ $prefix = $context['prefix'];
+ } elseif ( isset( $context['channel'] ) && $context['channel'] !== 'wfDebug' ) {
+ $prefix = "[{$context['channel']}] ";
+ }
+ if ( isset( $context['seconds_elapsed'] ) && isset( $context['memory_used'] ) ) {
+ $prefix .= "{$context['seconds_elapsed']} {$context['memory_used']} ";
+ }
+ $str = $prefix . $str;
+ }
+ self::$debug[] = rtrim( UtfNormal\Validator::cleanUp( $str ) );
}
}
@@ -529,12 +540,11 @@ class MWDebug {
MWDebug::log( 'MWDebug output complete' );
$debugInfo = self::getDebugInfo( $context );
- $result->setIndexedTagName( $debugInfo, 'debuginfo' );
- $result->setIndexedTagName( $debugInfo['log'], 'line' );
- $result->setIndexedTagName( $debugInfo['debugLog'], 'msg' );
- $result->setIndexedTagName( $debugInfo['queries'], 'query' );
- $result->setIndexedTagName( $debugInfo['includes'], 'queries' );
- $result->setIndexedTagName( $debugInfo['profile'], 'function' );
+ ApiResult::setIndexedTagName( $debugInfo, 'debuginfo' );
+ ApiResult::setIndexedTagName( $debugInfo['log'], 'line' );
+ ApiResult::setIndexedTagName( $debugInfo['debugLog'], 'msg' );
+ ApiResult::setIndexedTagName( $debugInfo['queries'], 'query' );
+ ApiResult::setIndexedTagName( $debugInfo['includes'], 'queries' );
$result->addValue( null, 'debuginfo', $debugInfo );
}
@@ -578,7 +588,6 @@ class MWDebug {
'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/debug/logger/LegacyLogger.php b/includes/debug/logger/LegacyLogger.php
new file mode 100644
index 00000000..edaef4a7
--- /dev/null
+++ b/includes/debug/logger/LegacyLogger.php
@@ -0,0 +1,380 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger;
+
+use DateTimeZone;
+use MWDebug;
+use Psr\Log\AbstractLogger;
+use Psr\Log\LogLevel;
+use UDPTransport;
+
+/**
+ * PSR-3 logger that mimics the historic implementation of MediaWiki's
+ * wfErrorLog logging implementation.
+ *
+ * This logger is configured by the following global configuration variables:
+ * - `$wgDebugLogFile`
+ * - `$wgDebugLogGroups`
+ * - `$wgDBerrorLog`
+ * - `$wgDBerrorLogTZ`
+ *
+ * See documentation in DefaultSettings.php for detailed explanations of each
+ * variable.
+ *
+ * @see \MediaWiki\Logger\LoggerFactory
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class LegacyLogger extends AbstractLogger {
+
+ /**
+ * @var string $channel
+ */
+ protected $channel;
+
+ /**
+ * Convert Psr\Log\LogLevel constants into int for sane comparisons
+ * These are the same values that Monlog uses
+ *
+ * @var array
+ */
+ protected static $levelMapping = array(
+ LogLevel::DEBUG => 100,
+ LogLevel::INFO => 200,
+ LogLevel::NOTICE => 250,
+ LogLevel::WARNING => 300,
+ LogLevel::ERROR => 400,
+ LogLevel::CRITICAL => 500,
+ LogLevel::ALERT => 550,
+ LogLevel::EMERGENCY => 600,
+ );
+
+
+ /**
+ * @param string $channel
+ */
+ public function __construct( $channel ) {
+ $this->channel = $channel;
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param string|int $level
+ * @param string $message
+ * @param array $context
+ */
+ public function log( $level, $message, array $context = array() ) {
+ if ( self::shouldEmit( $this->channel, $message, $level, $context ) ) {
+ $text = self::format( $this->channel, $message, $context );
+ $destination = self::destination( $this->channel, $message, $context );
+ self::emit( $text, $destination );
+ }
+ // Add to debug toolbar
+ MWDebug::debugMsg( $message, array( 'channel' => $this->channel ) + $context );
+ }
+
+
+ /**
+ * Determine if the given message should be emitted or not.
+ *
+ * @param string $channel
+ * @param string $message
+ * @param string|int $level Psr\Log\LogEvent constant or Monlog level int
+ * @param array $context
+ * @return bool True if message should be sent to disk/network, false
+ * otherwise
+ */
+ public static function shouldEmit( $channel, $message, $level, $context ) {
+ global $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
+
+ if ( $channel === 'wfLogDBError' ) {
+ // wfLogDBError messages are emitted if a database log location is
+ // specfied.
+ $shouldEmit = (bool)$wgDBerrorLog;
+
+ } elseif ( $channel === 'wfErrorLog' ) {
+ // All messages on the wfErrorLog channel should be emitted.
+ $shouldEmit = true;
+
+ } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
+ $logConfig = $wgDebugLogGroups[$channel];
+
+ if ( is_array( $logConfig ) ) {
+ $shouldEmit = true;
+ if ( isset( $logConfig['sample'] ) ) {
+ // Emit randomly with a 1 in 'sample' chance for each message.
+ $shouldEmit = mt_rand( 1, $logConfig['sample'] ) === 1;
+ }
+
+ if ( isset( $logConfig['level'] ) ) {
+ if ( is_string( $level ) ) {
+ $level = self::$levelMapping[$level];
+ }
+ $shouldEmit = $level >= self::$levelMapping[$logConfig['level']];
+ }
+ } else {
+ // Emit unless the config value is explictly false.
+ $shouldEmit = $logConfig !== false;
+ }
+
+ } elseif ( isset( $context['private'] ) && $context['private'] ) {
+ // Don't emit if the message didn't match previous checks based on
+ // the channel and the event is marked as private. This check
+ // discards messages sent via wfDebugLog() with dest == 'private'
+ // and no explicit wgDebugLogGroups configuration.
+ $shouldEmit = false;
+ } else {
+ // Default return value is the same as the historic wfDebug
+ // method: emit if $wgDebugLogFile has been set.
+ $shouldEmit = $wgDebugLogFile != '';
+ }
+
+ return $shouldEmit;
+ }
+
+
+ /**
+ * Format a message.
+ *
+ * Messages to the 'wfDebug', 'wfLogDBError' and 'wfErrorLog' channels
+ * receive special fomatting to mimic the historic output of the functions
+ * of the same name. All other channel values are formatted based on the
+ * historic output of the `wfDebugLog()` global function.
+ *
+ * @param string $channel
+ * @param string $message
+ * @param array $context
+ * @return string
+ */
+ public static function format( $channel, $message, $context ) {
+ global $wgDebugLogGroups;
+
+ if ( $channel === 'wfDebug' ) {
+ $text = self::formatAsWfDebug( $channel, $message, $context );
+
+ } elseif ( $channel === 'wfLogDBError' ) {
+ $text = self::formatAsWfLogDBError( $channel, $message, $context );
+
+ } elseif ( $channel === 'wfErrorLog' ) {
+ $text = "{$message}\n";
+
+ } elseif ( $channel === 'profileoutput' ) {
+ // Legacy wfLogProfilingData formatitng
+ $forward = '';
+ if ( isset( $context['forwarded_for'] )) {
+ $forward = " forwarded for {$context['forwarded_for']}";
+ }
+ if ( isset( $context['client_ip'] ) ) {
+ $forward .= " client IP {$context['client_ip']}";
+ }
+ if ( isset( $context['from'] ) ) {
+ $forward .= " from {$context['from']}";
+ }
+ if ( $forward ) {
+ $forward = "\t(proxied via {$context['proxy']}{$forward})";
+ }
+ if ( $context['anon'] ) {
+ $forward .= ' anon';
+ }
+ if ( !isset( $context['url'] ) ) {
+ $context['url'] = 'n/a';
+ }
+
+ $log = sprintf( "%s\t%04.3f\t%s%s\n",
+ gmdate( 'YmdHis' ), $context['elapsed'], $context['url'], $forward );
+
+ $text = self::formatAsWfDebugLog(
+ $channel, $log . $context['output'], $context );
+
+ } elseif ( !isset( $wgDebugLogGroups[$channel] ) ) {
+ $text = self::formatAsWfDebug(
+ $channel, "[{$channel}] {$message}", $context );
+
+ } else {
+ // Default formatting is wfDebugLog's historic style
+ $text = self::formatAsWfDebugLog( $channel, $message, $context );
+ }
+
+ return self::interpolate( $text, $context );
+ }
+
+
+ /**
+ * Format a message as `wfDebug()` would have formatted it.
+ *
+ * @param string $channel
+ * @param string $message
+ * @param array $context
+ * @return string
+ */
+ protected static function formatAsWfDebug( $channel, $message, $context ) {
+ $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $message );
+ if ( isset( $context['seconds_elapsed'] ) ) {
+ // Prepend elapsed request time and real memory usage with two
+ // trailing spaces.
+ $text = "{$context['seconds_elapsed']} {$context['memory_used']} {$text}";
+ }
+ if ( isset( $context['prefix'] ) ) {
+ $text = "{$context['prefix']}{$text}";
+ }
+ return "{$text}\n";
+ }
+
+
+ /**
+ * Format a message as `wfLogDBError()` would have formatted it.
+ *
+ * @param string $channel
+ * @param string $message
+ * @param array $context
+ * @return string
+ */
+ protected static function formatAsWfLogDBError( $channel, $message, $context ) {
+ global $wgDBerrorLogTZ;
+ static $cachedTimezone = null;
+
+ if ( $wgDBerrorLogTZ && !$cachedTimezone ) {
+ $cachedTimezone = new DateTimeZone( $wgDBerrorLogTZ );
+ }
+
+ // Workaround for https://bugs.php.net/bug.php?id=52063
+ // Can be removed when min PHP > 5.3.6
+ if ( $cachedTimezone === null ) {
+ $d = date_create( 'now' );
+ } else {
+ $d = date_create( 'now', $cachedTimezone );
+ }
+ $date = $d->format( 'D M j G:i:s T Y' );
+
+ $host = wfHostname();
+ $wiki = wfWikiID();
+
+ $text = "{$date}\t{$host}\t{$wiki}\t{$message}\n";
+ return $text;
+ }
+
+
+ /**
+ * Format a message as `wfDebugLog() would have formatted it.
+ *
+ * @param string $channel
+ * @param string $message
+ * @param array $context
+ */
+ protected static function formatAsWfDebugLog( $channel, $message, $context ) {
+ $time = wfTimestamp( TS_DB );
+ $wiki = wfWikiID();
+ $host = wfHostname();
+ $text = "{$time} {$host} {$wiki}: {$message}\n";
+ return $text;
+ }
+
+
+ /**
+ * Interpolate placeholders in logging message.
+ *
+ * @param string $message
+ * @param array $context
+ * @return string Interpolated message
+ */
+ public static function interpolate( $message, array $context ) {
+ if ( strpos( $message, '{' ) !== false ) {
+ $replace = array();
+ foreach ( $context as $key => $val ) {
+ $replace['{' . $key . '}'] = $val;
+ }
+ $message = strtr( $message, $replace );
+ }
+ return $message;
+ }
+
+
+ /**
+ * Select the appropriate log output destination for the given log event.
+ *
+ * If the event context contains 'destination'
+ *
+ * @param string $channel
+ * @param string $message
+ * @param array $context
+ * @return string
+ */
+ protected static function destination( $channel, $message, $context ) {
+ global $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
+
+ // Default destination is the debug log file as historically used by
+ // the wfDebug function.
+ $destination = $wgDebugLogFile;
+
+ if ( isset( $context['destination'] ) ) {
+ // Use destination explicitly provided in context
+ $destination = $context['destination'];
+
+ } elseif ( $channel === 'wfDebug' ) {
+ $destination = $wgDebugLogFile;
+
+ } elseif ( $channel === 'wfLogDBError' ) {
+ $destination = $wgDBerrorLog;
+
+ } elseif ( isset( $wgDebugLogGroups[$channel] ) ) {
+ $logConfig = $wgDebugLogGroups[$channel];
+
+ if ( is_array( $logConfig ) ) {
+ $destination = $logConfig['destination'];
+ } else {
+ $destination = strval( $logConfig );
+ }
+ }
+
+ return $destination;
+ }
+
+
+ /**
+ * Log to a file without getting "file size exceeded" signals.
+ *
+ * Can also log to 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 string $text
+ * @param string $file Filename
+ * @throws MWException
+ */
+ public static function emit( $text, $file ) {
+ if ( substr( $file, 0, 4 ) == 'udp:' ) {
+ $transport = UDPTransport::newFromString( $file );
+ $transport->emit( $text );
+ } else {
+ wfSuppressWarnings();
+ $exists = file_exists( $file );
+ $size = $exists ? filesize( $file ) : false;
+ if ( !$exists ||
+ ( $size !== false && $size + strlen( $text ) < 0x7fffffff )
+ ) {
+ file_put_contents( $file, $text, FILE_APPEND );
+ }
+ wfRestoreWarnings();
+ }
+ }
+
+}
diff --git a/includes/debug/logger/LegacySpi.php b/includes/debug/logger/LegacySpi.php
new file mode 100644
index 00000000..1bf39e41
--- /dev/null
+++ b/includes/debug/logger/LegacySpi.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger;
+
+/**
+ * LoggerFactory service provider that creates LegacyLogger instances.
+ *
+ * Usage:
+ * @code
+ * $wgMWLoggerDefaultSpi = array(
+ * 'class' => '\\MediaWiki\\Logger\\LegacySpi',
+ * );
+ * @endcode
+ *
+ * @see \MediaWiki\Logger\LoggerFactory
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class LegacySpi implements Spi {
+
+ /**
+ * @var array $singletons
+ */
+ protected $singletons = array();
+
+
+ /**
+ * Get a logger instance.
+ *
+ * @param string $channel Logging channel
+ * @return \Psr\Log\LoggerInterface Logger instance
+ */
+ public function getLogger( $channel ) {
+ if ( !isset( $this->singletons[$channel] ) ) {
+ $this->singletons[$channel] = new LegacyLogger( $channel );
+ }
+ return $this->singletons[$channel];
+ }
+
+}
diff --git a/includes/debug/logger/LoggerFactory.php b/includes/debug/logger/LoggerFactory.php
new file mode 100644
index 00000000..b3078b9a
--- /dev/null
+++ b/includes/debug/logger/LoggerFactory.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger;
+
+use ObjectFactory;
+
+/**
+ * PSR-3 logger instance factory.
+ *
+ * Creation of \Psr\Log\LoggerInterface instances is managed via the
+ * LoggerFactory::getInstance() static method which in turn delegates to the
+ * currently registered service provider.
+ *
+ * A service provider is any class implementing the Spi interface.
+ * There are two possible methods of registering a service provider. The
+ * LoggerFactory::registerProvider() static method can be called at any time
+ * to change the service provider. If LoggerFactory::getInstance() is called
+ * before any service provider has been registered, it will attempt to use the
+ * $wgMWLoggerDefaultSpi global to bootstrap Spi registration.
+ * $wgMWLoggerDefaultSpi is expected to be an array usable by
+ * ObjectFactory::getObjectFromSpec() to create a class.
+ *
+ * @see \MediaWiki\Logger\Spi
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class LoggerFactory {
+
+ /**
+ * Service provider.
+ * @var Spi $spi
+ */
+ private static $spi;
+
+
+ /**
+ * Register a service provider to create new \Psr\Log\LoggerInterface
+ * instances.
+ *
+ * @param Spi $provider Provider to register
+ */
+ public static function registerProvider( Spi $provider ) {
+ self::$spi = $provider;
+ }
+
+
+ /**
+ * Get the registered service provider.
+ *
+ * If called before any service provider has been registered, it will
+ * attempt to use the $wgMWLoggerDefaultSpi global to bootstrap
+ * Spi registration. $wgMWLoggerDefaultSpi is expected to be an
+ * array usable by ObjectFactory::getObjectFromSpec() to create a class.
+ *
+ * @return Spi
+ * @see registerProvider()
+ * @see ObjectFactory::getObjectFromSpec()
+ */
+ public static function getProvider() {
+ if ( self::$spi === null ) {
+ global $wgMWLoggerDefaultSpi;
+ $provider = ObjectFactory::getObjectFromSpec(
+ $wgMWLoggerDefaultSpi
+ );
+ self::registerProvider( $provider );
+ }
+ return self::$spi;
+ }
+
+
+ /**
+ * Get a named logger instance from the currently configured logger factory.
+ *
+ * @param string $channel Logger channel (name)
+ * @return \Psr\Log\LoggerInterface
+ */
+ public static function getInstance( $channel ) {
+ if ( !interface_exists( '\Psr\Log\LoggerInterface' ) ) {
+ $message = (
+ 'MediaWiki requires the <a href="https://github.com/php-fig/log">PSR-3 logging ' .
+ "library</a> to be present. This library is not embedded directly in MediaWiki's " .
+ "git repository and must be installed separately by the end user.\n\n" .
+ 'Please see <a href="https://www.mediawiki.org/wiki/Download_from_Git' .
+ '#Fetch_external_libraries">mediawiki.org</a> for help on installing ' .
+ 'the required components.'
+ );
+ echo $message;
+ trigger_error( $message, E_USER_ERROR );
+ die( 1 );
+ }
+
+ return self::getProvider()->getLogger( $channel );
+ }
+
+
+ /**
+ * Construction of utility class is not allowed.
+ */
+ private function __construct() {
+ // no-op
+ }
+}
diff --git a/includes/debug/logger/MonologSpi.php b/includes/debug/logger/MonologSpi.php
new file mode 100644
index 00000000..a07fdc4a
--- /dev/null
+++ b/includes/debug/logger/MonologSpi.php
@@ -0,0 +1,251 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger;
+
+use Monolog\Logger;
+use ObjectFactory;
+
+/**
+ * LoggerFactory service provider that creates loggers implemented by
+ * Monolog.
+ *
+ * Configured using an array of configuration data with the keys 'loggers',
+ * 'processors', 'handlers' and 'formatters'.
+ *
+ * The ['loggers']['@default'] configuration will be used to create loggers
+ * for any channel that isn't explicitly named in the 'loggers' configuration
+ * section.
+ *
+ * Configuration will most typically be provided in the $wgMWLoggerDefaultSpi
+ * global configuration variable used by LoggerFactory to construct its
+ * default SPI provider:
+ * @code
+ * $wgMWLoggerDefaultSpi = array(
+ * 'class' => '\\MediaWiki\\Logger\\MonologSpi',
+ * 'args' => array( array(
+ * 'loggers' => array(
+ * '@default' => array(
+ * 'processors' => array( 'wiki', 'psr', 'pid', 'uid', 'web' ),
+ * 'handlers' => array( 'stream' ),
+ * ),
+ * 'runJobs' => array(
+ * 'processors' => array( 'wiki', 'psr', 'pid' ),
+ * 'handlers' => array( 'stream' ),
+ * )
+ * ),
+ * 'processors' => array(
+ * 'wiki' => array(
+ * 'class' => '\\MediaWiki\\Logger\\Monolog\\WikiProcessor',
+ * ),
+ * 'psr' => array(
+ * 'class' => '\\Monolog\\Processor\\PsrLogMessageProcessor',
+ * ),
+ * 'pid' => array(
+ * 'class' => '\\Monolog\\Processor\\ProcessIdProcessor',
+ * ),
+ * 'uid' => array(
+ * 'class' => '\\Monolog\\Processor\\UidProcessor',
+ * ),
+ * 'web' => array(
+ * 'class' => '\\Monolog\\Processor\\WebProcessor',
+ * ),
+ * ),
+ * 'handlers' => array(
+ * 'stream' => array(
+ * 'class' => '\\Monolog\\Handler\\StreamHandler',
+ * 'args' => array( 'path/to/your.log' ),
+ * 'formatter' => 'line',
+ * ),
+ * 'redis' => array(
+ * 'class' => '\\Monolog\\Handler\\RedisHandler',
+ * 'args' => array( function() {
+ * $redis = new Redis();
+ * $redis->connect( '127.0.0.1', 6379 );
+ * return $redis;
+ * },
+ * 'logstash'
+ * ),
+ * 'formatter' => 'logstash',
+ * ),
+ * 'udp2log' => array(
+ * 'class' => '\\MediaWiki\\Logger\\Monolog\\LegacyHandler',
+ * 'args' => array(
+ * 'udp://127.0.0.1:8420/mediawiki
+ * ),
+ * 'formatter' => 'line',
+ * ),
+ * ),
+ * 'formatters' => array(
+ * 'line' => array(
+ * 'class' => '\\Monolog\\Formatter\\LineFormatter',
+ * ),
+ * 'logstash' => array(
+ * 'class' => '\\Monolog\\Formatter\\LogstashFormatter',
+ * 'args' => array( 'mediawiki', php_uname( 'n' ), null, '', 1 ),
+ * ),
+ * ),
+ * ) ),
+ * );
+ * @endcode
+ *
+ * @see https://github.com/Seldaek/monolog
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class MonologSpi implements Spi {
+
+ /**
+ * @var array $singletons
+ */
+ protected $singletons;
+
+ /**
+ * Configuration for creating new loggers.
+ * @var array $config
+ */
+ protected $config;
+
+
+ /**
+ * @param array $config Configuration data.
+ */
+ public function __construct( array $config ) {
+ $this->config = $config;
+ $this->reset();
+ }
+
+
+ /**
+ * Reset internal caches.
+ *
+ * This is public for use in unit tests. Under normal operation there should
+ * be no need to flush the caches.
+ */
+ public function reset() {
+ $this->singletons = array(
+ 'loggers' => array(),
+ 'handlers' => array(),
+ 'formatters' => array(),
+ 'processors' => array(),
+ );
+ }
+
+
+ /**
+ * Get a logger instance.
+ *
+ * Creates and caches a logger instance based on configuration found in the
+ * $wgMWLoggerMonologSpiConfig global. Subsequent request for the same channel
+ * name will return the cached instance.
+ *
+ * @param string $channel Logging channel
+ * @return \Psr\Log\LoggerInterface Logger instance
+ */
+ public function getLogger( $channel ) {
+ if ( !isset( $this->singletons['loggers'][$channel] ) ) {
+ // Fallback to using the '@default' configuration if an explict
+ // configuration for the requested channel isn't found.
+ $spec = isset( $this->config['loggers'][$channel] ) ?
+ $this->config['loggers'][$channel] :
+ $this->config['loggers']['@default'];
+
+ $monolog = $this->createLogger( $channel, $spec );
+ $this->singletons['loggers'][$channel] = $monolog;
+ }
+
+ return $this->singletons['loggers'][$channel];
+ }
+
+
+ /**
+ * Create a logger.
+ * @param string $channel Logger channel
+ * @param array $spec Configuration
+ * @return \Monolog\Logger
+ */
+ protected function createLogger( $channel, $spec ) {
+ $obj = new Logger( $channel );
+
+ if ( isset( $spec['processors'] ) ) {
+ foreach ( $spec['processors'] as $processor ) {
+ $obj->pushProcessor( $this->getProcessor( $processor ) );
+ }
+ }
+
+ if ( isset( $spec['handlers'] ) ) {
+ foreach ( $spec['handlers'] as $handler ) {
+ $obj->pushHandler( $this->getHandler( $handler ) );
+ }
+ }
+ return $obj;
+ }
+
+
+ /**
+ * Create or return cached processor.
+ * @param string $name Processor name
+ * @return callable
+ */
+ public function getProcessor( $name ) {
+ if ( !isset( $this->singletons['processors'][$name] ) ) {
+ $spec = $this->config['processors'][$name];
+ $processor = ObjectFactory::getObjectFromSpec( $spec );
+ $this->singletons['processors'][$name] = $processor;
+ }
+ return $this->singletons['processors'][$name];
+ }
+
+
+ /**
+ * Create or return cached handler.
+ * @param string $name Processor name
+ * @return \Monolog\Handler\HandlerInterface
+ */
+ public function getHandler( $name ) {
+ if ( !isset( $this->singletons['handlers'][$name] ) ) {
+ $spec = $this->config['handlers'][$name];
+ $handler = ObjectFactory::getObjectFromSpec( $spec );
+ if ( isset( $spec['formatter'] ) ) {
+ $handler->setFormatter(
+ $this->getFormatter( $spec['formatter'] )
+ );
+ }
+ $this->singletons['handlers'][$name] = $handler;
+ }
+ return $this->singletons['handlers'][$name];
+ }
+
+
+ /**
+ * Create or return cached formatter.
+ * @param string $name Formatter name
+ * @return \Monolog\Formatter\FormatterInterface
+ */
+ public function getFormatter( $name ) {
+ if ( !isset( $this->singletons['formatters'][$name] ) ) {
+ $spec = $this->config['formatters'][$name];
+ $formatter = ObjectFactory::getObjectFromSpec( $spec );
+ $this->singletons['formatters'][$name] = $formatter;
+ }
+ return $this->singletons['formatters'][$name];
+ }
+}
diff --git a/includes/debug/logger/NullSpi.php b/includes/debug/logger/NullSpi.php
new file mode 100644
index 00000000..a82d2c4c
--- /dev/null
+++ b/includes/debug/logger/NullSpi.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger;
+
+use Psr\Log\NullLogger;
+
+/**
+ * LoggerFactory service provider that creates \Psr\Log\NullLogger
+ * instances. A NullLogger silently discards all log events sent to it.
+ *
+ * Usage:
+ * @code
+ * $wgMWLoggerDefaultSpi = array(
+ * 'class' => '\\MediaWiki\\Logger\\NullSpi',
+ * );
+ * @endcode
+ *
+ * @see \MediaWiki\Logger\LoggerFactory
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class NullSpi implements Spi {
+
+ /**
+ * @var \Psr\Log\NullLogger $singleton
+ */
+ protected $singleton;
+
+
+ public function __construct() {
+ $this->singleton = new NullLogger();
+ }
+
+
+ /**
+ * Get a logger instance.
+ *
+ * @param string $channel Logging channel
+ * @return \Psr\Log\NullLogger Logger instance
+ */
+ public function getLogger( $channel ) {
+ return $this->singleton;
+ }
+
+}
diff --git a/includes/debug/logger/Spi.php b/includes/debug/logger/Spi.php
new file mode 100644
index 00000000..044789f2
--- /dev/null
+++ b/includes/debug/logger/Spi.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger;
+
+/**
+ * Service provider interface for \Psr\Log\LoggerInterface implementation
+ * libraries.
+ *
+ * MediaWiki can be configured to use a class implementing this interface to
+ * create new \Psr\Log\LoggerInterface instances via either the
+ * $wgMWLoggerDefaultSpi global variable or code that constructs an instance
+ * and registers it via the LoggerFactory::registerProvider() static method.
+ *
+ * @see \MediaWiki\Logger\LoggerFactory
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+interface Spi {
+
+ /**
+ * Get a logger instance.
+ *
+ * @param string $channel Logging channel
+ * @return \Psr\Log\LoggerInterface Logger instance
+ */
+ public function getLogger( $channel );
+
+}
diff --git a/includes/debug/logger/monolog/LegacyFormatter.php b/includes/debug/logger/monolog/LegacyFormatter.php
new file mode 100644
index 00000000..9ec15cb8
--- /dev/null
+++ b/includes/debug/logger/monolog/LegacyFormatter.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use MediaWiki\Logger\LegacyLogger;
+use Monolog\Formatter\NormalizerFormatter;
+
+/**
+ * Log message formatter that mimics the legacy log message formatting of
+ * `wfDebug`, `wfDebugLog`, `wfLogDBError` and `wfErrorLog` global functions by
+ * delegating the formatting to \MediaWiki\Logger\LegacyLogger.
+ *
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2013 Bryan Davis and Wikimedia Foundation.
+ * @see \MediaWiki\Logger\LegacyLogger
+ */
+class LegacyFormatter extends NormalizerFormatter {
+
+ public function __construct() {
+ parent::__construct( 'c' );
+ }
+
+ public function format( array $record ) {
+ $normalized = parent::format( $record );
+ return LegacyLogger::format(
+ $normalized['channel'], $normalized['message'], $normalized
+ );
+ }
+}
diff --git a/includes/debug/logger/monolog/LegacyHandler.php b/includes/debug/logger/monolog/LegacyHandler.php
new file mode 100644
index 00000000..8405819d
--- /dev/null
+++ b/includes/debug/logger/monolog/LegacyHandler.php
@@ -0,0 +1,243 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use LogicException;
+use MediaWiki\Logger\LegacyLogger;
+use Monolog\Handler\AbstractProcessingHandler;
+use Monolog\Logger;
+use UnexpectedValueException;
+
+/**
+ * Log handler that replicates the behavior of MediaWiki's wfErrorLog()
+ * logging service. Log output can be directed to a local file, a PHP stream,
+ * or a udp2log server.
+ *
+ * For udp2log output, the stream specification must have the form:
+ * "udp://HOST:PORT[/PREFIX]"
+ * where:
+ * - HOST: IPv4, IPv6 or hostname
+ * - PORT: server port
+ * - PREFIX: optional (but recommended) prefix telling udp2log how to route
+ * the log event. The special prefix "{channel}" will use the log event's
+ * channel as the prefix value.
+ *
+ * When not targeting a udp2log stream this class will act as a drop-in
+ * replacement for Monolog's StreamHandler.
+ *
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2013 Bryan Davis and Wikimedia Foundation.
+ */
+class LegacyHandler extends AbstractProcessingHandler {
+
+ /**
+ * Log sink descriptor
+ * @var string $uri
+ */
+ protected $uri;
+
+ /**
+ * Filter log events using legacy rules
+ * @var bool $useLegacyFilter
+ */
+ protected $useLegacyFilter;
+
+ /**
+ * Log sink
+ * @var resource $sink
+ */
+ protected $sink;
+
+ /**
+ * @var string $error
+ */
+ protected $error;
+
+ /**
+ * @var string $host
+ */
+ protected $host;
+
+ /**
+ * @var int $port
+ */
+ protected $port;
+
+ /**
+ * @var string $prefix
+ */
+ protected $prefix;
+
+
+ /**
+ * @param string $stream Stream URI
+ * @param bool $useLegacyFilter Filter log events using legacy rules
+ * @param int $level Minimum logging level that will trigger handler
+ * @param bool $bubble Can handled meesages bubble up the handler stack?
+ */
+ public function __construct(
+ $stream,
+ $useLegacyFilter = false,
+ $level = Logger::DEBUG,
+ $bubble = true
+ ) {
+ parent::__construct( $level, $bubble );
+ $this->uri = $stream;
+ $this->useLegacyFilter = $useLegacyFilter;
+ }
+
+ /**
+ * Open the log sink described by our stream URI.
+ */
+ protected function openSink() {
+ if ( !$this->uri ) {
+ throw new LogicException(
+ 'Missing stream uri, the stream can not be opened.' );
+ }
+ $this->error = null;
+ set_error_handler( array( $this, 'errorTrap' ) );
+
+ if ( substr( $this->uri, 0, 4 ) == 'udp:' ) {
+ $parsed = parse_url( $this->uri );
+ if ( !isset( $parsed['host'] ) ) {
+ throw new UnexpectedValueException( sprintf(
+ 'Udp transport "%s" must specify a host', $this->uri
+ ) );
+ }
+ if ( !isset( $parsed['port'] ) ) {
+ throw new UnexpectedValueException( sprintf(
+ 'Udp transport "%s" must specify a port', $this->uri
+ ) );
+ }
+
+ $this->host = $parsed['host'];
+ $this->port = $parsed['port'];
+ $this->prefix = '';
+
+ if ( isset( $parsed['path'] ) ) {
+ $this->prefix = ltrim( $parsed['path'], '/' );
+ }
+
+ if ( filter_var( $this->host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
+ $domain = AF_INET6;
+
+ } else {
+ $domain = AF_INET;
+ }
+
+ $this->sink = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
+
+ } else {
+ $this->sink = fopen( $this->uri, 'a' );
+ }
+ restore_error_handler();
+
+ if ( !is_resource( $this->sink ) ) {
+ $this->sink = null;
+ throw new UnexpectedValueException( sprintf(
+ 'The stream or file "%s" could not be opened: %s',
+ $this->uri, $this->error
+ ) );
+ }
+ }
+
+
+ /**
+ * Custom error handler.
+ * @param int $code Error number
+ * @param string $msg Error message
+ */
+ protected function errorTrap( $code, $msg ) {
+ $this->error = $msg;
+ }
+
+
+ /**
+ * Should we use UDP to send messages to the sink?
+ * @return bool
+ */
+ protected function useUdp() {
+ return $this->host !== null;
+ }
+
+
+ protected function write( array $record ) {
+ if ( $this->useLegacyFilter &&
+ !LegacyLogger::shouldEmit(
+ $record['channel'], $record['message'],
+ $record['level'], $record
+ ) ) {
+ // Do not write record if we are enforcing legacy rules and they
+ // do not pass this message. This used to be done in isHandling(),
+ // but Monolog 1.12.0 made a breaking change that removed access
+ // to the needed channel and context information.
+ return;
+ }
+
+ if ( $this->sink === null ) {
+ $this->openSink();
+ }
+
+ $text = (string)$record['formatted'];
+ if ( $this->useUdp() ) {
+
+ // Clean it up for the multiplexer
+ if ( $this->prefix !== '' ) {
+ $leader = ( $this->prefix === '{channel}' ) ?
+ $record['channel'] : $this->prefix;
+ $text = preg_replace( '/^/m', "{$leader} ", $text );
+
+ // Limit to 64KB
+ if ( strlen( $text ) > 65506 ) {
+ $text = substr( $text, 0, 65506 );
+ }
+
+ if ( substr( $text, -1 ) != "\n" ) {
+ $text .= "\n";
+ }
+
+ } elseif ( strlen( $text ) > 65507 ) {
+ $text = substr( $text, 0, 65507 );
+ }
+
+ socket_sendto(
+ $this->sink, $text, strlen( $text ), 0, $this->host, $this->port
+ );
+
+ } else {
+ fwrite( $this->sink, $text );
+ }
+ }
+
+
+ public function close() {
+ if ( is_resource( $this->sink ) ) {
+ if ( $this->useUdp() ) {
+ socket_close( $this->sink );
+
+ } else {
+ fclose( $this->sink );
+ }
+ }
+ $this->sink = null;
+ }
+}
diff --git a/includes/debug/logger/monolog/SyslogHandler.php b/includes/debug/logger/monolog/SyslogHandler.php
new file mode 100644
index 00000000..008efbc1
--- /dev/null
+++ b/includes/debug/logger/monolog/SyslogHandler.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use Monolog\Handler\SyslogUdpHandler;
+use Monolog\Logger;
+
+/**
+ * Log handler that supports sending log events to a syslog server using RFC
+ * 3164 formatted UDP packets.
+ *
+ * Monolog's SyslogUdpHandler creates a partial RFC 5424 header (PRI and
+ * VERSION) and relies on the associated formatter to complete the header and
+ * message payload. This makes using it with a fixed format formatter like
+ * Monolog\Formatter\LogstashFormatter impossible. Additionally, the direct
+ * syslog input for Logstash only handles RFC 3164 syslog packets.
+ *
+ * This Handler should work with any Formatter. The formatted message will be
+ * prepended with an RFC 3164 message header and a partial message body. The
+ * resulting packet will looks something like:
+ *
+ * <PRI>DATETIME HOSTNAME PROGRAM: MESSAGE
+ *
+ * This format works as input to rsyslog and can also be processed by the
+ * default Logstash syslog input handler.
+ *
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2015 Bryan Davis and Wikimedia Foundation.
+ */
+class SyslogHandler extends SyslogUdpHandler {
+
+ /**
+ * @var string $appname
+ */
+ private $appname;
+
+ /**
+ * @var string $hostname
+ */
+ private $hostname;
+
+
+ /**
+ * @param string $appname Application name to report to syslog
+ * @param string $host Syslog host
+ * @param int $port Syslog port
+ * @param int $facility Syslog message facility
+ * @param string $level The minimum logging level at which this handler
+ * will be triggered
+ * @param bool $bubble Whether the messages that are handled can bubble up
+ * the stack or not
+ */
+ public function __construct(
+ $appname,
+ $host,
+ $port = 514,
+ $facility = LOG_USER,
+ $level = Logger::DEBUG,
+ $bubble = true
+ ) {
+ parent::__construct( $host, $port, $facility, $level, $bubble );
+ $this->appname = $appname;
+ $this->hostname = php_uname( 'n' );
+ }
+
+ protected function makeCommonSyslogHeader( $severity ) {
+ $pri = $severity + $this->facility;
+
+ // Goofy date format courtesy of RFC 3164 :(
+ // RFC 3164 actually specifies that the day of month should be space
+ // padded rather than unpadded but this seems to work with rsyslog and
+ // Logstash.
+ $timestamp = date( 'M j H:i:s' );
+
+ return "<{$pri}>{$timestamp} {$this->hostname} {$this->appname}: ";
+ }
+}
diff --git a/includes/debug/logger/monolog/WikiProcessor.php b/includes/debug/logger/monolog/WikiProcessor.php
new file mode 100644
index 00000000..a52f6366
--- /dev/null
+++ b/includes/debug/logger/monolog/WikiProcessor.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+/**
+ * Injects `wfHostname()` and `wfWikiID()` in all records.
+ *
+ * @since 1.25
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2013 Bryan Davis and Wikimedia Foundation.
+ */
+class WikiProcessor {
+
+ /**
+ * @param array $record
+ * @return array
+ */
+ public function __invoke( array $record ) {
+ $record['extra'] = array_merge(
+ $record['extra'],
+ array(
+ 'host' => wfHostname(),
+ 'wiki' => wfWikiID(),
+ )
+ );
+ return $record;
+ }
+
+}
diff --git a/includes/deferred/DeferredUpdates.php b/includes/deferred/DeferredUpdates.php
index b0c1899f..42816ddc 100644
--- a/includes/deferred/DeferredUpdates.php
+++ b/includes/deferred/DeferredUpdates.php
@@ -82,13 +82,10 @@ class DeferredUpdates {
public static function doUpdates( $commit = '' ) {
global $wgDeferredUpdateList;
- wfProfileIn( __METHOD__ );
-
$updates = array_merge( $wgDeferredUpdateList, self::$updates );
// No need to get master connections in case of empty updates array
if ( !count( $updates ) ) {
- wfProfileOut( __METHOD__ );
return;
}
@@ -110,7 +107,7 @@ class DeferredUpdates {
if ( $doCommit && $dbw->trxLevel() ) {
$dbw->commit( __METHOD__, 'flush' );
}
- } catch ( MWException $e ) {
+ } catch ( Exception $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.
@@ -122,7 +119,6 @@ class DeferredUpdates {
$updates = array_merge( $wgDeferredUpdateList, self::$updates );
}
- wfProfileOut( __METHOD__ );
}
/**
diff --git a/includes/deferred/HTMLCacheUpdate.php b/includes/deferred/HTMLCacheUpdate.php
index 54fa5943..79a10e68 100644
--- a/includes/deferred/HTMLCacheUpdate.php
+++ b/includes/deferred/HTMLCacheUpdate.php
@@ -43,12 +43,11 @@ class HTMLCacheUpdate implements DeferrableUpdate {
}
public function doUpdate() {
- wfProfileIn( __METHOD__ );
-
$job = new HTMLCacheUpdateJob(
$this->mTitle,
array(
'table' => $this->mTable,
+ 'recursive' => true
) + Job::newRootJobParams( // "overall" refresh links job info
"htmlCacheUpdate:{$this->mTable}:{$this->mTitle->getPrefixedText()}"
)
@@ -64,7 +63,5 @@ class HTMLCacheUpdate implements DeferrableUpdate {
$job->run(); // just do the purge query now
} );
}
-
- wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/deferred/LinksUpdate.php b/includes/deferred/LinksUpdate.php
index 45d26648..e4f00e75 100644
--- a/includes/deferred/LinksUpdate.php
+++ b/includes/deferred/LinksUpdate.php
@@ -58,12 +58,6 @@ class LinksUpdate extends SqlDataUpdate {
/** @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;
@@ -140,20 +134,19 @@ class LinksUpdate extends SqlDataUpdate {
$this->mRecursive = $recursive;
- wfRunHooks( 'LinksUpdateConstructed', array( &$this ) );
+ Hooks::run( 'LinksUpdateConstructed', array( &$this ) );
}
/**
* Update link tables with outgoing links from an updated article
*/
public function doUpdate() {
- wfRunHooks( 'LinksUpdate', array( &$this ) );
+ Hooks::run( 'LinksUpdate', array( &$this ) );
$this->doIncrementalUpdate();
- wfRunHooks( 'LinksUpdateComplete', array( &$this ) );
+ Hooks::run( 'LinksUpdateComplete', array( &$this ) );
}
protected function doIncrementalUpdate() {
- wfProfileIn( __METHOD__ );
# Page links
$existing = $this->getExistingLinks();
@@ -227,7 +220,6 @@ class LinksUpdate extends SqlDataUpdate {
$this->queueRecursiveJobs();
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -236,12 +228,24 @@ class LinksUpdate extends SqlDataUpdate {
* Which means do LinksUpdate on all pages that include the current page,
* using the job queue.
*/
- function queueRecursiveJobs() {
+ protected 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' );
}
+
+ $bc = $this->mTitle->getBacklinkCache();
+ // Get jobs for cascade-protected backlinks for a high priority queue.
+ // If meta-templates change to using a new template, the new template
+ // should be implicitly protected as soon as possible, if applicable.
+ // These jobs duplicate a subset of the above ones, but can run sooner.
+ // Which ever runs first generally no-ops the other one.
+ $jobs = array();
+ foreach ( $bc->getCascadeProtectedLinks() as $title ) {
+ $jobs[] = new RefreshLinksJob( $title, array( 'prioritize' => true ) );
+ }
+ JobQueueGroup::singleton()->push( $jobs );
}
/**
@@ -251,7 +255,6 @@ class LinksUpdate extends SqlDataUpdate {
* @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 RefreshLinksJob(
$title,
@@ -262,10 +265,10 @@ class LinksUpdate extends SqlDataUpdate {
"refreshlinks:{$table}:{$title->getPrefixedText()}"
)
);
+
JobQueueGroup::singleton()->push( $job );
JobQueueGroup::singleton()->deduplicateRootJob( $job );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -339,7 +342,7 @@ class LinksUpdate extends SqlDataUpdate {
}
if ( count( $insertions ) ) {
$this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
- wfRunHooks( 'LinksUpdateAfterInsert', array( $this, $table, $insertions ) );
+ Hooks::run( 'LinksUpdateAfterInsert', array( $this, $table, $insertions ) );
}
}
diff --git a/includes/deferred/SearchUpdate.php b/includes/deferred/SearchUpdate.php
index 5d084afd..ba14f099 100644
--- a/includes/deferred/SearchUpdate.php
+++ b/includes/deferred/SearchUpdate.php
@@ -78,9 +78,7 @@ class SearchUpdate implements DeferrableUpdate {
return;
}
- wfProfileIn( __METHOD__ );
-
- $page = WikiPage::newFromId( $this->id, WikiPage::READ_LATEST );
+ $page = WikiPage::newFromID( $this->id, WikiPage::READ_LATEST );
foreach ( SearchEngine::getSearchTypes() as $type ) {
$search = SearchEngine::create( $type );
@@ -108,7 +106,6 @@ class SearchUpdate implements DeferrableUpdate {
$search->update( $this->id, $normalTitle, $search->normalizeText( $text ) );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -125,7 +122,6 @@ class SearchUpdate implements DeferrableUpdate {
$text = $wgContLang->normalizeForSearch( $text );
$lc = SearchEngine::legalSearchChars() . '&#;';
- wfProfileIn( __METHOD__ . '-regexps' );
$text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/",
' ', $wgContLang->lc( " " . $text . " " ) ); # Strip HTML markup
$text = preg_replace( "/(^|\\n)==\\s*([^\\n]+)\\s*==(\\s)/sD",
@@ -172,7 +168,6 @@ class SearchUpdate implements DeferrableUpdate {
# Strip wiki '' and '''
$text = preg_replace( "/''[']*/", " ", $text );
- wfProfileOut( __METHOD__ . '-regexps' );
return $text;
}
diff --git a/includes/deferred/SiteStatsUpdate.php b/includes/deferred/SiteStatsUpdate.php
index 7bfafee8..97a17c39 100644
--- a/includes/deferred/SiteStatsUpdate.php
+++ b/includes/deferred/SiteStatsUpdate.php
@@ -23,9 +23,6 @@
*/
class SiteStatsUpdate implements DeferrableUpdate {
/** @var int */
- protected $views = 0;
-
- /** @var int */
protected $edits = 0;
/** @var int */
@@ -42,7 +39,6 @@ class SiteStatsUpdate implements DeferrableUpdate {
// @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;
@@ -100,7 +96,6 @@ class SiteStatsUpdate implements DeferrableUpdate {
}
$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']['-'] );
@@ -110,7 +105,6 @@ class SiteStatsUpdate implements DeferrableUpdate {
// 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 );
@@ -160,7 +154,6 @@ class SiteStatsUpdate implements DeferrableUpdate {
}
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 );
@@ -226,7 +219,7 @@ class SiteStatsUpdate implements DeferrableUpdate {
global $wgMemc;
$pending = array();
- foreach ( array( 'ss_total_views', 'ss_total_edits',
+ foreach ( array( 'ss_total_edits',
'ss_good_articles', 'ss_total_pages', 'ss_users', 'ss_images' ) as $type
) {
// Get pending increments and pending decrements
diff --git a/includes/deferred/SqlDataUpdate.php b/includes/deferred/SqlDataUpdate.php
index 9c58503f..49164e33 100644
--- a/includes/deferred/SqlDataUpdate.php
+++ b/includes/deferred/SqlDataUpdate.php
@@ -31,11 +31,11 @@
* the beginTransaction() and commitTransaction() methods.
*/
abstract class SqlDataUpdate extends DataUpdate {
- /** @var DatabaseBase Database connection reference */
+ /** @var IDatabase Database connection reference */
protected $mDb;
/** @var array SELECT options to be used (array) */
- protected $mOptions;
+ protected $mOptions = array();
/** @var bool Whether a transaction is open on this object (internal use only!) */
private $mHasTransaction;
@@ -51,19 +51,9 @@ abstract class SqlDataUpdate extends DataUpdate {
* transaction is already in progress, see beginTransaction() for details.
*/
public function __construct( $withTransaction = true ) {
- global $wgAntiLockFlags;
-
parent::__construct();
- if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
- $this->mOptions = array();
- } else {
- $this->mOptions = array( 'FOR UPDATE' );
- }
-
- // @todo Get connection only when it's needed? Make sure that doesn't
- // break anything, especially transactions!
- $this->mDb = wfGetDB( DB_MASTER );
+ $this->mDb = wfGetLB()->getLazyConnectionRef( DB_MASTER );
$this->mWithTransaction = $withTransaction;
$this->mHasTransaction = false;
@@ -121,39 +111,40 @@ abstract class SqlDataUpdate extends DataUpdate {
return;
}
- /**
- * Determine which pages need to be updated
- * This is necessary to prevent the job queue from smashing the DB with
- * large numbers of concurrent invalidations of the same page
- */
- $now = $this->mDb->timestamp();
- $ids = array();
- $res = $this->mDb->select( 'page', array( 'page_id' ),
- array(
- 'page_namespace' => $namespace,
- 'page_title' => $dbkeys,
- 'page_touched < ' . $this->mDb->addQuotes( $now )
- ), __METHOD__
- );
-
- foreach ( $res as $row ) {
- $ids[] = $row->page_id;
- }
-
- if ( $ids === array() ) {
- return;
- }
-
- /**
- * Do the update
- * We still need the page_touched condition, in case the row has changed since
- * the non-locking select above.
- */
- $this->mDb->update( 'page', array( 'page_touched' => $now ),
- array(
- 'page_id' => $ids,
- 'page_touched < ' . $this->mDb->addQuotes( $now )
- ), __METHOD__
- );
+ $dbw = $this->mDb;
+ $dbw->onTransactionPreCommitOrIdle( function() use ( $dbw, $namespace, $dbkeys ) {
+ /**
+ * Determine which pages need to be updated
+ * This is necessary to prevent the job queue from smashing the DB with
+ * large numbers of concurrent invalidations of the same page
+ */
+ $now = $dbw->timestamp();
+ $ids = $dbw->selectFieldValues( 'page',
+ 'page_id',
+ array(
+ 'page_namespace' => $namespace,
+ 'page_title' => $dbkeys,
+ 'page_touched < ' . $dbw->addQuotes( $now )
+ ),
+ __METHOD__
+ );
+
+ if ( $ids === array() ) {
+ return;
+ }
+
+ /**
+ * Do the update
+ * We still need the page_touched condition, in case the row has changed since
+ * the non-locking select above.
+ */
+ $dbw->update( 'page',
+ array( 'page_touched' => $now ),
+ array(
+ 'page_id' => $ids,
+ 'page_touched < ' . $dbw->addQuotes( $now )
+ ), __METHOD__
+ );
+ } );
}
}
diff --git a/includes/deferred/SquidUpdate.php b/includes/deferred/SquidUpdate.php
index 0dcff44a..950a264a 100644
--- a/includes/deferred/SquidUpdate.php
+++ b/includes/deferred/SquidUpdate.php
@@ -52,41 +52,6 @@ class SquidUpdate {
}
/**
- * Create a SquidUpdate from the given Title object.
- *
- * The resulting SquidUpdate will purge the given Title's URLs as well as
- * the pages that link to it. Capped at $wgMaxSquidPurgeTitles total URLs.
- *
- * @param Title $title
- * @return SquidUpdate
- */
- public static function newFromLinksTo( Title $title ) {
- global $wgMaxSquidPurgeTitles;
- wfProfileIn( __METHOD__ );
-
- # Get a list of URLs linking to this page
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'links', 'page' ),
- array( 'page_namespace', 'page_title' ),
- array(
- 'pl_namespace' => $title->getNamespace(),
- 'pl_title' => $title->getDBkey(),
- 'pl_from=page_id' ),
- __METHOD__ );
- $blurlArr = $title->getSquidURLs();
- if ( $res->numRows() <= $wgMaxSquidPurgeTitles ) {
- foreach ( $res as $BL ) {
- $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title );
- $blurlArr[] = $tobj->getInternalURL();
- }
- }
-
- wfProfileOut( __METHOD__ );
-
- return new SquidUpdate( $blurlArr );
- }
-
- /**
* Create a SquidUpdate from an array of Title objects, or a TitleArray object
*
* @param array $titles
@@ -145,8 +110,6 @@ class SquidUpdate {
self::HTCPPurge( $urlArr );
}
- wfProfileIn( __METHOD__ );
-
// Remove duplicate URLs
$urlArr = array_unique( $urlArr );
// Maximum number of parallel connections per squid
@@ -172,7 +135,6 @@ class SquidUpdate {
}
$pool->run();
- wfProfileOut( __METHOD__ );
}
/**
@@ -183,7 +145,6 @@ class SquidUpdate {
*/
public static function HTCPPurge( $urlArr ) {
global $wgHTCPRouting, $wgHTCPMulticastTTL;
- wfProfileIn( __METHOD__ );
// HTCP CLR operation
$htcpOpCLR = 4;
@@ -201,7 +162,6 @@ class SquidUpdate {
$errstr = socket_strerror( socket_last_error() );
wfDebugLog( 'squid', __METHOD__ .
": Error opening UDP socket: $errstr" );
- wfProfileOut( __METHOD__ );
return;
}
@@ -223,7 +183,6 @@ class SquidUpdate {
foreach ( $urlArr as $url ) {
if ( !is_string( $url ) ) {
- wfProfileOut( __METHOD__ );
throw new MWException( 'Bad purge URL' );
}
$url = self::expand( $url );
@@ -240,7 +199,6 @@ class SquidUpdate {
}
foreach ( $conf as $subconf ) {
if ( !isset( $subconf['host'] ) || !isset( $subconf['port'] ) ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "Invalid HTCP rule for URL $url\n" );
}
}
@@ -272,7 +230,6 @@ class SquidUpdate {
$subconf['host'], $subconf['port'] );
}
}
- wfProfileOut( __METHOD__ );
}
/**
diff --git a/includes/deferred/ViewCountUpdate.php b/includes/deferred/ViewCountUpdate.php
deleted file mode 100644
index 8282295b..00000000
--- a/includes/deferred/ViewCountUpdate.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-/**
- * Update for the 'page_counter' field
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Update for the 'page_counter' field, when $wgDisableCounters is false.
- *
- * Depending on $wgHitcounterUpdateFreq, this will directly increment the
- * 'page_counter' field or use the 'hitcounter' table and then collect the data
- * 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 int $id Page ID to increment the view count
- */
- public function __construct( $id ) {
- $this->id = intval( $id );
- }
-
- /**
- * Run the update
- */
- public function doUpdate() {
- global $wgHitcounterUpdateFreq;
-
- $dbw = wfGetDB( DB_MASTER );
-
- if ( $wgHitcounterUpdateFreq <= 1 || $dbw->getType() == 'sqlite' ) {
- $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 ) {
- MWExceptionHandler::logException( $e );
- }
- }
-
- protected function collect() {
- global $wgHitcounterUpdateFreq;
-
- $dbw = wfGetDB( DB_MASTER );
-
- $rown = $dbw->selectField( 'hitcounter', 'COUNT(*)', array(), __METHOD__ );
- if ( $rown < $wgHitcounterUpdateFreq ) {
- return;
- }
-
- wfProfileIn( __METHOD__ . '-collect' );
- $old_user_abort = ignore_user_abort( true );
-
- $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__ );
- $dbw->delete( 'hitcounter', '*', __METHOD__ );
- $dbw->unlockTables( __METHOD__ );
-
- if ( $dbType == 'mysql' ) {
- $dbw->query( "UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n " .
- 'WHERE page_id = hc_id', __METHOD__ );
- } else {
- $dbw->query( "UPDATE $pageTable SET page_counter=page_counter + hc_n " .
- "FROM $acchitsTable WHERE page_id = hc_id", __METHOD__ );
- }
- $dbw->query( "DROP TABLE $acchitsTable", __METHOD__ );
-
- ignore_user_abort( $old_user_abort );
- wfProfileOut( __METHOD__ . '-collect' );
- }
-}
diff --git a/includes/diff/DairikiDiff.php b/includes/diff/DairikiDiff.php
index a4c0168f..d327433f 100644
--- a/includes/diff/DairikiDiff.php
+++ b/includes/diff/DairikiDiff.php
@@ -189,7 +189,7 @@ class DiffOpChange extends DiffOp {
* More ideas are taken from:
* http://www.ics.uci.edu/~eppstein/161/960229.html
*
- * Some ideas are (and a bit of code) are from from analyze.c, from GNU
+ * Some ideas (and a bit of code) are 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
*
@@ -222,7 +222,6 @@ class DiffEngine {
* @return DiffOp[]
*/
public function diff( $from_lines, $to_lines ) {
- wfProfileIn( __METHOD__ );
// Diff and store locally
$this->diffLocal( $from_lines, $to_lines );
@@ -272,7 +271,6 @@ class DiffEngine {
$edits[] = new DiffOpAdd( $add );
}
}
- wfProfileOut( __METHOD__ );
return $edits;
}
@@ -283,7 +281,6 @@ class DiffEngine {
*/
private function diffLocal( $from_lines, $to_lines ) {
global $wgExternalDiffEngine;
- wfProfileIn( __METHOD__ );
if ( $wgExternalDiffEngine == 'wikidiff3' ) {
// wikidiff3
@@ -346,7 +343,6 @@ class DiffEngine {
// Find the LCS.
$this->compareSeq( 0, count( $this->xv ), 0, count( $this->yv ) );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -582,7 +578,6 @@ class DiffEngine {
* This is extracted verbatim from analyze.c (GNU diffutils-2.7).
*/
private function shiftBoundaries( $lines, &$changed, $other_changed ) {
- wfProfileIn( __METHOD__ );
$i = 0;
$j = 0;
@@ -697,7 +692,6 @@ class DiffEngine {
assert( '$j >= 0 && !$other_changed[$j]' );
}
}
- wfProfileOut( __METHOD__ );
}
}
@@ -858,7 +852,6 @@ class MappedDiff extends Diff {
*/
public function __construct( $from_lines, $to_lines,
$mapped_from_lines, $mapped_to_lines ) {
- wfProfileIn( __METHOD__ );
assert( 'count( $from_lines ) == count( $mapped_from_lines )' );
assert( 'count( $to_lines ) == count( $mapped_to_lines )' );
@@ -880,7 +873,6 @@ class MappedDiff extends Diff {
$yi += count( $closing );
}
}
- wfProfileOut( __METHOD__ );
}
}
@@ -981,14 +973,12 @@ class WordLevelDiff extends MappedDiff {
* @param string[] $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 );
parent::__construct( $orig_words, $closing_words,
$orig_stripped, $closing_stripped );
- wfProfileOut( __METHOD__ );
}
/**
@@ -997,7 +987,6 @@ class WordLevelDiff extends MappedDiff {
* @return array[]
*/
private function split( $lines ) {
- wfProfileIn( __METHOD__ );
$words = array();
$stripped = array();
@@ -1028,7 +1017,6 @@ class WordLevelDiff extends MappedDiff {
}
}
}
- wfProfileOut( __METHOD__ );
return array( $words, $stripped );
}
@@ -1037,7 +1025,6 @@ class WordLevelDiff extends MappedDiff {
* @return string[]
*/
public function orig() {
- wfProfileIn( __METHOD__ );
$orig = new HWLDFWordAccumulator;
foreach ( $this->edits as $edit ) {
@@ -1048,7 +1035,6 @@ class WordLevelDiff extends MappedDiff {
}
}
$lines = $orig->getLines();
- wfProfileOut( __METHOD__ );
return $lines;
}
@@ -1057,7 +1043,6 @@ class WordLevelDiff extends MappedDiff {
* @return string[]
*/
public function closing() {
- wfProfileIn( __METHOD__ );
$closing = new HWLDFWordAccumulator;
foreach ( $this->edits as $edit ) {
@@ -1068,7 +1053,6 @@ class WordLevelDiff extends MappedDiff {
}
}
$lines = $closing->getLines();
- wfProfileOut( __METHOD__ );
return $lines;
}
diff --git a/includes/diff/DiffFormatter.php b/includes/diff/DiffFormatter.php
index 40df0d75..33ca931f 100644
--- a/includes/diff/DiffFormatter.php
+++ b/includes/diff/DiffFormatter.php
@@ -57,7 +57,6 @@ abstract class DiffFormatter {
* @return string The formatted output.
*/
public function format( $diff ) {
- wfProfileIn( __METHOD__ );
$xi = $yi = 1;
$block = false;
@@ -115,7 +114,6 @@ abstract class DiffFormatter {
}
$end = $this->endDiff();
- wfProfileOut( __METHOD__ );
return $end;
}
@@ -130,7 +128,6 @@ abstract class DiffFormatter {
* @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' ) {
@@ -146,7 +143,6 @@ abstract class DiffFormatter {
}
}
$this->endBlock();
- wfProfileOut( __METHOD__ );
}
protected function startDiff() {
diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php
index 50e08ca1..77bbd36a 100644
--- a/includes/diff/DifferenceEngine.php
+++ b/includes/diff/DifferenceEngine.php
@@ -94,6 +94,10 @@ class DifferenceEngine extends ContextSource {
/** @var bool Show rev_deleted content if allowed */
protected $unhide = false;
+
+ /** @var bool Refresh the diff cache */
+ protected $mRefreshCache = false;
+
/**#@-*/
/**
@@ -224,12 +228,14 @@ class DifferenceEngine extends ContextSource {
}
$out->setPageTitle( $this->msg( 'errorpagetitle' ) );
- $out->addWikiMsg( 'difference-missing-revision',
- $this->getLanguage()->listToText( $missing ), count( $missing ) );
+ $msg = $this->msg( 'difference-missing-revision' )
+ ->params( $this->getLanguage()->listToText( $missing ) )
+ ->numParams( count( $missing ) )
+ ->parseAsBlock();
+ $out->addHtml( $msg );
}
public function showDiffPage( $diffOnly = false ) {
- wfProfileIn( __METHOD__ );
# Allow frames except in certain special cases
$out = $this->getOutput();
@@ -238,7 +244,6 @@ class DifferenceEngine extends ContextSource {
if ( !$this->loadRevisionData() ) {
$this->showMissingRevision();
- wfProfileOut( __METHOD__ );
return;
}
@@ -250,7 +255,6 @@ class DifferenceEngine extends ContextSource {
$this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
}
if ( count( $permErrors ) ) {
- wfProfileOut( __METHOD__ );
throw new PermissionsError( 'read', $permErrors );
}
@@ -280,7 +284,7 @@ class DifferenceEngine extends ContextSource {
$samePage = true;
$oldHeader = '';
} else {
- wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
+ Hooks::run( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
if ( $this->mNewPage->equals( $this->mOldPage ) ) {
$out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
@@ -384,7 +388,7 @@ class DifferenceEngine extends ContextSource {
$rdel = $this->revisionDeleteLink( $this->mNewRev );
# Allow extensions to define their own revision tools
- wfRunHooks( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools, $this->mOldRev ) );
+ Hooks::run( 'DiffRevisionTools', array( $this->mNewRev, &$revisionTools, $this->mOldRev ) );
$formattedRevisionTools = array();
// Put each one in parentheses (poor man's button)
foreach ( $revisionTools as $key => $tool ) {
@@ -451,7 +455,6 @@ class DifferenceEngine extends ContextSource {
$this->renderNewRevision();
}
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -488,7 +491,7 @@ class DifferenceEngine extends ContextSource {
array( 'USE INDEX' => 'rc_timestamp' )
);
- if ( $change && $change->getPerformer()->getName() !== $user->getName() ) {
+ if ( $change && !$change->getPerformer()->equals( $user ) ) {
$rcid = $change->getAttribute( 'rc_id' );
} else {
// None found or the page has been created by the current user.
@@ -544,15 +547,14 @@ class DifferenceEngine extends ContextSource {
* Show the new revision of the page.
*/
public function renderNewRevision() {
- wfProfileIn( __METHOD__ );
$out = $this->getOutput();
$revHeader = $this->getRevisionHeader( $this->mNewRev );
# Add "current version as of X" title
- $out->addHTML( "<hr class='diff-hr' />
+ $out->addHTML( "<hr class='diff-hr' id='mw-oldid' />
<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 ) ) ) {
+ if ( Hooks::run( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
$this->loadNewText();
$out->setRevisionId( $this->mNewid );
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
@@ -572,7 +574,7 @@ class DifferenceEngine extends ContextSource {
$out->addParserOutputContent( $po );
}
}
- } elseif ( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
+ } elseif ( !Hooks::run( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// Handled by extension
} elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// NOTE: deprecated hook, B/C only
@@ -602,13 +604,12 @@ class DifferenceEngine extends ContextSource {
# Add redundant patrol link on bottom...
$out->addHTML( $this->markPatrolledLink() );
- wfProfileOut( __METHOD__ );
}
protected function getParserOutput( WikiPage $page, Revision $rev ) {
$parserOptions = $page->makeParserOptions( $this->getContext() );
- if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
+ if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( 'edit', $this->getUser() ) ) {
$parserOptions->setEditSection( false );
}
@@ -681,23 +682,19 @@ class DifferenceEngine extends ContextSource {
*/
public function getDiffBody() {
global $wgMemc;
- wfProfileIn( __METHOD__ );
$this->mCacheHit = true;
// 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() )
) {
- wfProfileOut( __METHOD__ );
return false;
} elseif ( $this->mNewRev &&
!$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -705,7 +702,6 @@ class DifferenceEngine extends ContextSource {
if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
&& $this->mOldRev->getID() == $this->mNewRev->getID() )
) {
- wfProfileOut( __METHOD__ );
return '';
}
@@ -721,7 +717,6 @@ class DifferenceEngine extends ContextSource {
wfIncrStats( 'diff_cache_hit' );
$difftext = $this->localiseLineNumbers( $difftext );
$difftext .= "\n<!-- diff cache key $key -->\n";
- wfProfileOut( __METHOD__ );
return $difftext;
}
@@ -731,7 +726,6 @@ class DifferenceEngine extends ContextSource {
// Loadtext is permission safe, this just clears out the diff
if ( !$this->loadText() ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -739,7 +733,7 @@ class DifferenceEngine extends ContextSource {
$difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
// Save to cache for 7 days
- if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
+ if ( !Hooks::run( 'AbortDiffCache', array( &$this ) ) ) {
wfIncrStats( 'diff_uncacheable' );
} elseif ( $key !== false && $difftext !== false ) {
wfIncrStats( 'diff_cache_miss' );
@@ -751,7 +745,6 @@ class DifferenceEngine extends ContextSource {
if ( $difftext !== false ) {
$difftext = $this->localiseLineNumbers( $difftext );
}
- wfProfileOut( __METHOD__ );
return $difftext;
}
@@ -837,8 +830,6 @@ class DifferenceEngine extends ContextSource {
public function generateTextDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
- wfProfileIn( __METHOD__ );
-
$otext = str_replace( "\r\n", "\n", $otext );
$ntext = str_replace( "\r\n", "\n", $ntext );
@@ -847,7 +838,6 @@ class DifferenceEngine extends ContextSource {
# input text to be HTML-escaped already
$otext = htmlspecialchars( $wgContLang->segmentForDiff( $otext ) );
$ntext = htmlspecialchars( $wgContLang->segmentForDiff( $ntext ) );
- wfProfileOut( __METHOD__ );
return $wgContLang->unsegmentForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
$this->debug( 'wikidiff1' );
@@ -856,11 +846,8 @@ class DifferenceEngine extends ContextSource {
if ( $wgExternalDiffEngine == 'wikidiff2' && function_exists( 'wikidiff2_do_diff' ) ) {
# Better external diff engine, the 2 may some day be dropped
# This one does the escaping and segmenting itself
- wfProfileIn( 'wikidiff2_do_diff' );
$text = wikidiff2_do_diff( $otext, $ntext, 2 );
$text .= $this->debug( 'wikidiff2' );
- wfProfileOut( 'wikidiff2_do_diff' );
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -872,13 +859,11 @@ class DifferenceEngine extends ContextSource {
$tempFile1 = fopen( $tempName1, "w" );
if ( !$tempFile1 ) {
- wfProfileOut( __METHOD__ );
return false;
}
$tempFile2 = fopen( $tempName2, "w" );
if ( !$tempFile2 ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -887,13 +872,10 @@ class DifferenceEngine extends ContextSource {
fclose( $tempFile1 );
fclose( $tempFile2 );
$cmd = wfEscapeShellArg( $wgExternalDiffEngine, $tempName1, $tempName2 );
- wfProfileIn( __METHOD__ . "-shellexec" );
$difftext = wfShellExec( $cmd );
$difftext .= $this->debug( "external $wgExternalDiffEngine" );
- wfProfileOut( __METHOD__ . "-shellexec" );
unlink( $tempName1 );
unlink( $tempName2 );
- wfProfileOut( __METHOD__ );
return $difftext;
}
@@ -903,8 +885,7 @@ class DifferenceEngine extends ContextSource {
$nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
$diffs = new Diff( $ota, $nta );
$formatter = new TableDiffFormatter();
- $difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
- wfProfileOut( __METHOD__ );
+ $difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) );
return $difftext;
}
@@ -985,7 +966,7 @@ class DifferenceEngine extends ContextSource {
$users = $this->mNewPage->getAuthorsBetween( $oldRev, $newRev, $limit );
$numUsers = count( $users );
- if ( $numUsers == 1 && $users[0] == $newRev->getRawUserText() ) {
+ if ( $numUsers == 1 && $users[0] == $newRev->getUserText( Revision::RAW ) ) {
$numUsers = 0; // special case to say "by the same user" instead of "by one other user"
}
@@ -1059,7 +1040,7 @@ class DifferenceEngine extends ContextSource {
$key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold';
$msg = $this->msg( $key )->escaped();
$editLink = $this->msg( 'parentheses' )->rawParams(
- Linker::linkKnown( $title, $msg, array( ), $editQuery ) )->plain();
+ Linker::linkKnown( $title, $msg, array( ), $editQuery ) )->escaped();
$header .= ' ' . Html::rawElement(
'span',
array( 'class' => 'mw-diff-edit' ),
@@ -1221,7 +1202,7 @@ class DifferenceEngine extends ContextSource {
$this->mNewid = 0;
}
- wfRunHooks(
+ Hooks::run(
'NewDifferenceEngine',
array( $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new )
);
diff --git a/includes/diff/TableDiffFormatter.php b/includes/diff/TableDiffFormatter.php
index db7318f2..5d0183ff 100644
--- a/includes/diff/TableDiffFormatter.php
+++ b/includes/diff/TableDiffFormatter.php
@@ -62,7 +62,7 @@ class TableDiffFormatter extends DiffFormatter {
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" .
+ $r = '<tr><td colspan="2" class="diff-lineno" id="L' . $xbeg . '" ><!--LINE ' . $xbeg . "--></td>\n" .
'<td colspan="2" class="diff-lineno"><!--LINE ' . $ybeg . "--></td></tr>\n";
return $r;
@@ -190,7 +190,6 @@ class TableDiffFormatter extends DiffFormatter {
* @param string[] $closing
*/
protected function changed( $orig, $closing ) {
- wfProfileIn( __METHOD__ );
$diff = new WordLevelDiff( $orig, $closing );
$del = $diff->orig();
@@ -208,7 +207,6 @@ class TableDiffFormatter extends DiffFormatter {
echo '<tr>' . $this->emptyLine() .
$this->addedLine( $line ) . "</tr>\n";
}
- wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/exception/HttpError.php b/includes/exception/HttpError.php
index 6ab6e039..b81c5731 100644
--- a/includes/exception/HttpError.php
+++ b/includes/exception/HttpError.php
@@ -18,6 +18,8 @@
* @file
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* Show an error that looks like an HTTP server error.
* Replacement for wfHttpError().
@@ -43,6 +45,19 @@ class HttpError extends MWException {
}
/**
+ * We don't want the default exception logging as we got our own logging set
+ * up in self::report.
+ *
+ * @see MWException::isLoggable
+ *
+ * @since 1.24
+ * @return bool
+ */
+ public function isLoggable() {
+ return false;
+ }
+
+ /**
* Returns the HTTP status code supplied to the constructor.
*
* @return int
@@ -52,11 +67,13 @@ class HttpError extends MWException {
}
/**
- * Report the HTTP error.
+ * Report and log the HTTP error.
* Sends the appropriate HTTP status code and outputs an
* HTML page with an error message.
*/
public function report() {
+ $this->doLog();
+
$httpMessage = HttpStatus::getMessage( $this->httpCode );
header( "Status: {$this->httpCode} {$httpMessage}", true, $this->httpCode );
@@ -65,6 +82,29 @@ class HttpError extends MWException {
print $this->getHTML();
}
+ private function doLog() {
+ $logger = LoggerFactory::getInstance( 'HttpError' );
+ $content = $this->content;
+
+ if ( $content instanceof Message ) {
+ $content = $content->text();
+ }
+
+ $context = array(
+ 'file' => $this->getFile(),
+ 'line' => $this->getLine(),
+ 'http_code' => $this->httpCode,
+ );
+
+ $logMsg = "$content ({http_code}) from {file}:{line}";
+
+ if ( $this->getStatusCode() < 500 ) {
+ $logger->info( $logMsg, $context );
+ } else {
+ $logger->error( $logMsg, $context );
+ }
+ }
+
/**
* Returns HTML for reporting the HTTP error.
* This will be a minimal but complete HTML document.
diff --git a/includes/exception/MWException.php b/includes/exception/MWException.php
index 074128f8..478fead1 100644
--- a/includes/exception/MWException.php
+++ b/includes/exception/MWException.php
@@ -117,10 +117,12 @@ class MWException extends Exception {
$args = array_slice( func_get_args(), 2 );
if ( $this->useMessageCache() ) {
- return wfMessage( $key, $args )->text();
- } else {
- return wfMsgReplaceArgs( $fallback, $args );
+ try {
+ return wfMessage( $key, $args )->text();
+ } catch ( Exception $e ) {
+ }
}
+ return wfMsgReplaceArgs( $fallback, $args );
}
/**
@@ -139,10 +141,17 @@ class MWException extends Exception {
nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
"</p>\n";
} else {
+ $logId = MWExceptionHandler::getLogId( $this );
+ $type = get_class( $this );
return "<div class=\"errorbox\">" .
- '[' . MWExceptionHandler::getLogId( $this ) . '] ' .
- gmdate( 'Y-m-d H:i:s' ) .
- ": Fatal exception of type " . get_class( $this ) . "</div>\n" .
+ '[' . $logId . '] ' .
+ gmdate( 'Y-m-d H:i:s' ) . ": " .
+ $this->msg( "internalerror-fatal-exception",
+ "Fatal exception of type $1",
+ $type,
+ $logId,
+ MWExceptionHandler::getURL( $this )
+ ) . "</div>\n" .
"<!-- Set \$wgShowExceptionDetails = true; " .
"at the bottom of LocalSettings.php to show detailed " .
"debugging information. -->";
@@ -222,8 +231,6 @@ class MWException extends Exception {
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 ) );
diff --git a/includes/exception/MWExceptionHandler.php b/includes/exception/MWExceptionHandler.php
index 71917e13..c50b6c8c 100644
--- a/includes/exception/MWExceptionHandler.php
+++ b/includes/exception/MWExceptionHandler.php
@@ -23,11 +23,25 @@
* @ingroup Exception
*/
class MWExceptionHandler {
+
+ protected static $reservedMemory;
+ protected static $fatalErrorTypes = array(
+ E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR,
+ /* HHVM's FATAL_ERROR level */ 16777217,
+ );
+
/**
- * Install an exception handler for MediaWiki exception types.
+ * Install handlers with PHP.
*/
public static function installHandler() {
- set_exception_handler( array( 'MWExceptionHandler', 'handle' ) );
+ set_exception_handler( array( 'MWExceptionHandler', 'handleException' ) );
+ set_error_handler( array( 'MWExceptionHandler', 'handleError' ) );
+
+ // Reserve 16k of memory so we can report OOM fatals
+ self::$reservedMemory = str_repeat( ' ', 16384 );
+ register_shutdown_function(
+ array( 'MWExceptionHandler', 'handleFatalError' )
+ );
}
/**
@@ -45,7 +59,7 @@ class MWExceptionHandler {
$e->report();
} catch ( Exception $e2 ) {
// Exception occurred from within exception handler
- // Show a simpler error message for the original exception,
+ // Show a simpler message for the original exception,
// don't try to invoke report()
$message = "MediaWiki internal error.\n\n";
@@ -69,8 +83,7 @@ class MWExceptionHandler {
}
}
} else {
- $message = "Unexpected non-MediaWiki exception encountered, of type \"" .
- get_class( $e ) . "\"";
+ $message = "Exception encountered, of type \"" . get_class( $e ) . "\"";
if ( $wgShowExceptionDetails ) {
$message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
@@ -82,6 +95,7 @@ class MWExceptionHandler {
} else {
echo nl2br( htmlspecialchars( $message ) ) . "\n";
}
+
}
}
@@ -106,6 +120,7 @@ class MWExceptionHandler {
* 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
*/
@@ -126,34 +141,132 @@ class MWExceptionHandler {
*
* try {
* ...
- * } catch ( MWException $e ) {
+ * } catch ( Exception $e ) {
* $e->report();
* } catch ( Exception $e ) {
* echo $e->__toString();
* }
+ *
+ * @since 1.25
* @param Exception $e
*/
- public static function handle( $e ) {
- global $wgFullyInitialised;
-
- self::rollbackMasterChangesAndLog( $e );
+ public static function handleException( Exception $e ) {
+ try {
+ // Rollback DBs to avoid transaction notices. This may fail
+ // to rollback some DB due to connection issues or exceptions.
+ // However, any sane DB driver will rollback implicitly anyway.
+ self::rollbackMasterChangesAndLog( $e );
+ } catch ( DBError $e2 ) {
+ // If the DB is unreacheable, rollback() will throw an error
+ // and the error report() method might need messages from the DB,
+ // which would result in an exception loop. PHP may escalate such
+ // errors to "Exception thrown without a stack frame" fatals, but
+ // it's better to be explicit here.
+ self::logException( $e2 );
+ }
+ self::logException( $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 );
}
/**
+ * @since 1.25
+ * @param int $level Error level raised
+ * @param string $message
+ * @param string $file
+ * @param int $line
+ */
+ public static function handleError( $level, $message, $file = null, $line = null ) {
+ // Map error constant to error name (reverse-engineer PHP error reporting)
+ $channel = 'error';
+ switch ( $level ) {
+ case E_ERROR:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ case E_USER_ERROR:
+ case E_RECOVERABLE_ERROR:
+ case E_PARSE:
+ $levelName = 'Error';
+ $channel = 'fatal';
+ break;
+ case E_WARNING:
+ case E_CORE_WARNING:
+ case E_COMPILE_WARNING:
+ case E_USER_WARNING:
+ $levelName = 'Warning';
+ break;
+ case E_NOTICE:
+ case E_USER_NOTICE:
+ $levelName = 'Notice';
+ break;
+ case E_STRICT:
+ $levelName = 'Strict Standards';
+ break;
+ case E_DEPRECATED:
+ case E_USER_DEPRECATED:
+ $levelName = 'Deprecated';
+ break;
+ case /* HHVM's FATAL_ERROR */ 16777217:
+ $levelName = 'Fatal';
+ $channel = 'fatal';
+ break;
+ default:
+ $levelName = 'Unknown error';
+ break;
+ }
+
+ $e = new ErrorException( "PHP $levelName: $message", 0, $level, $file, $line );
+ self::logError( $e, $channel );
+
+ // This handler is for logging only. Return false will instruct PHP
+ // to continue regular handling.
+ return false;
+ }
+
+
+ /**
+ * Look for a fatal error as the cause of the request termination and log
+ * as an exception.
+ *
+ * Special handling is included for missing class errors as they may
+ * indicate that the user needs to install 3rd-party libraries via
+ * Composer or other means.
+ *
+ * @since 1.25
+ */
+ public static function handleFatalError() {
+ self::$reservedMemory = null;
+ $lastError = error_get_last();
+
+ if ( $lastError &&
+ isset( $lastError['type'] ) &&
+ in_array( $lastError['type'], self::$fatalErrorTypes )
+ ) {
+ $msg = "Fatal Error: {$lastError['message']}";
+ // HHVM: Class undefined: foo
+ // PHP5: Class 'foo' not found
+ if ( preg_match( "/Class (undefined: \w+|'\w+' not found)/",
+ $lastError['message']
+ ) ) {
+ // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+ $msg = <<<TXT
+{$msg}
+
+MediaWiki or an installed extension requires this class but it is not embedded directly in MediaWiki's git repository and must be installed separately by the end user.
+
+Please see <a href="https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a> for help on installing the required components.
+TXT;
+ // @codingStandardsIgnoreEnd
+ }
+ $e = new ErrorException( $msg, 0, $lastError['type'] );
+ self::logError( $e, 'fatal' );
+ }
+ }
+
+ /**
* Generate a string representation of an exception's stack trace
*
* Like Exception::getTraceAsString, but replaces argument values with
@@ -217,7 +330,7 @@ class MWExceptionHandler {
}
/**
- * Get the ID for this error.
+ * Get the ID for this exception.
*
* 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.
@@ -238,7 +351,7 @@ class MWExceptionHandler {
* returns the requested URL. Otherwise, returns false.
*
* @since 1.23
- * @return string|bool
+ * @return string|false
*/
public static function getURL() {
global $wgRequest;
@@ -249,8 +362,7 @@ class MWExceptionHandler {
}
/**
- * Return the requested URL and point to file and line number from which the
- * exception occurred.
+ * Get a message formatting the exception message and its origin.
*
* @since 1.22
* @param Exception $e
@@ -258,12 +370,13 @@ class MWExceptionHandler {
*/
public static function getLogMessage( Exception $e ) {
$id = self::getLogId( $e );
+ $type = get_class( $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";
+ return "[$id] $url $type from line $line of $file: $message";
}
/**
@@ -285,6 +398,7 @@ class MWExceptionHandler {
* @code
* {
* "id": "c41fb419",
+ * "type": "MWException",
* "file": "/var/www/mediawiki/includes/cache/MessageCache.php",
* "line": 704,
* "message": "Non-string key given",
@@ -296,6 +410,7 @@ class MWExceptionHandler {
* @code
* {
* "id": "dc457938",
+ * "type": "MWException",
* "file": "/vagrant/mediawiki/includes/cache/MessageCache.php",
* "line": 704,
* "message": "Non-string key given",
@@ -315,18 +430,24 @@ class MWExceptionHandler {
* @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
+ * @return string|false 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 ),
+ 'type' => get_class( $e ),
'file' => $e->getFile(),
'line' => $e->getLine(),
'message' => $e->getMessage(),
);
+ if ( $e instanceof ErrorException && ( error_reporting() & $e->getSeverity() ) === 0 ) {
+ // Flag surpressed errors
+ $exceptionData['suppressed'] = true;
+ }
+
// 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
@@ -345,7 +466,7 @@ class MWExceptionHandler {
* 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.
+ * it is also used to handle PHP exceptions or exceptions from other libraries.
*
* @since 1.22
* @param Exception $e
@@ -366,7 +487,33 @@ class MWExceptionHandler {
wfDebugLog( 'exception-json', $json, 'private' );
}
}
-
}
+ /**
+ * Log an exception that wasn't thrown but made to wrap an error.
+ *
+ * @since 1.25
+ * @param ErrorException $e
+ * @param string $channel
+ */
+ protected static function logError( ErrorException $e, $channel ) {
+ global $wgLogExceptionBacktrace;
+
+ // The set_error_handler callback is independent from error_reporting.
+ // Filter out unwanted errors manually (e.g. when wfSuppressWarnings is active).
+ if ( ( error_reporting() & $e->getSeverity() ) !== 0 ) {
+ $log = self::getLogMessage( $e );
+ if ( $wgLogExceptionBacktrace ) {
+ wfDebugLog( $channel, $log . "\n" . $e->getTraceAsString() );
+ } else {
+ wfDebugLog( $channel, $log );
+ }
+ }
+
+ // Include all errors in the json log (surpressed errors will be flagged)
+ $json = self::jsonSerializeException( $e, false, FormatJson::ALL_OK );
+ if ( $json !== false ) {
+ wfDebugLog( "$channel-json", $json, 'private' );
+ }
+ }
}
diff --git a/includes/TimestampException.php b/includes/exception/TimestampException.php
index b9c0c35c..b9c0c35c 100644
--- a/includes/TimestampException.php
+++ b/includes/exception/TimestampException.php
diff --git a/includes/exception/UserNotLoggedIn.php b/includes/exception/UserNotLoggedIn.php
index 03ba0b20..02fca3d8 100644
--- a/includes/exception/UserNotLoggedIn.php
+++ b/includes/exception/UserNotLoggedIn.php
@@ -25,8 +25,9 @@
* '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.
+ * constructor has to be explicitly added to LoginForm::validErrorMessages or with
+ * the LoginFormValidErrorMessages hook. Otherwise, the user will just be shown the message
+ * rather than redirected.
*
* @par Example:
* @code
@@ -52,7 +53,8 @@
class UserNotLoggedIn extends ErrorPageError {
/**
- * @note The value of the $reasonMsg parameter must be put into LoginForm::validErrorMessages
+ * @note The value of the $reasonMsg parameter must be put into LoginForm::validErrorMessages or
+ * set with the LoginFormValidErrorMessages Hook.
* 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.
@@ -77,7 +79,7 @@ class UserNotLoggedIn extends ErrorPageError {
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 ) ) {
+ if ( !in_array( $this->msg, LoginForm::getValidErrorMessages() ) ) {
parent::report();
}
diff --git a/includes/externalstore/ExternalStore.php b/includes/externalstore/ExternalStore.php
index 688130e0..ea04cc85 100644
--- a/includes/externalstore/ExternalStore.php
+++ b/includes/externalstore/ExternalStore.php
@@ -197,7 +197,7 @@ class ExternalStore {
}
try {
$url = $store->store( $path, $data ); // Try to save the object
- } catch ( MWException $error ) {
+ } catch ( Exception $error ) {
$url = false;
}
if ( strlen( $url ) ) {
diff --git a/includes/externalstore/ExternalStoreHttp.php b/includes/externalstore/ExternalStoreHttp.php
index 345c17be..00030d85 100644
--- a/includes/externalstore/ExternalStoreHttp.php
+++ b/includes/externalstore/ExternalStoreHttp.php
@@ -31,7 +31,7 @@ class ExternalStoreHttp extends ExternalStoreMedium {
* @see ExternalStoreMedium::fetchFromURL()
*/
public function fetchFromURL( $url ) {
- return Http::get( $url );
+ return Http::get( $url, array(), __METHOD__ );
}
/**
diff --git a/includes/filebackend/FSFile.php b/includes/filebackend/FSFile.php
index 1659c62a..6ee9b2e0 100644
--- a/includes/filebackend/FSFile.php
+++ b/includes/filebackend/FSFile.php
@@ -104,7 +104,6 @@ class FSFile {
* @return array
*/
public function getProps( $ext = true ) {
- wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . ": Getting file info for $this->path\n" );
$info = self::placeholderProps();
@@ -146,8 +145,6 @@ class FSFile {
wfDebug( __METHOD__ . ": $this->path NOT FOUND!\n" );
}
- wfProfileOut( __METHOD__ );
-
return $info;
}
@@ -201,10 +198,8 @@ class FSFile {
* @return bool|string False on failure
*/
public function getSha1Base36( $recache = false ) {
- wfProfileIn( __METHOD__ );
if ( $this->sha1Base36 !== null && !$recache ) {
- wfProfileOut( __METHOD__ );
return $this->sha1Base36;
}
@@ -217,8 +212,6 @@ class FSFile {
$this->sha1Base36 = wfBaseConvert( $this->sha1Base36, 16, 36, 31 );
}
- wfProfileOut( __METHOD__ );
-
return $this->sha1Base36;
}
diff --git a/includes/filebackend/FSFileBackend.php b/includes/filebackend/FSFileBackend.php
index b99ffb62..07370ad2 100644
--- a/includes/filebackend/FSFileBackend.php
+++ b/includes/filebackend/FSFileBackend.php
@@ -451,10 +451,13 @@ class FSFileBackend extends FileBackendStore {
// Create the directory and its parents as needed...
$this->trapWarnings();
if ( !wfMkdirParents( $dir ) ) {
+ wfDebugLog( 'FSFileBackend', __METHOD__ . ": cannot create directory $dir" );
$status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
} elseif ( !is_writable( $dir ) ) {
+ wfDebugLog( 'FSFileBackend', __METHOD__ . ": directory $dir is read-only" );
$status->fatal( 'directoryreadonlyerror', $params['dir'] );
} elseif ( !is_readable( $dir ) ) {
+ wfDebugLog( 'FSFileBackend', __METHOD__ . ": directory $dir is not readable" );
$status->fatal( 'directorynotreadableerror', $params['dir'] );
}
$this->untrapWarnings();
@@ -678,6 +681,11 @@ class FSFileBackend extends FileBackendStore {
return false;
}
+ /**
+ * @param FileBackendStoreOpHandle[] $fileOpHandles
+ *
+ * @return Status[]
+ */
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
$statuses = array();
diff --git a/includes/filebackend/FileBackend.php b/includes/filebackend/FileBackend.php
index 8c0a61a1..b87e26d3 100644
--- a/includes/filebackend/FileBackend.php
+++ b/includes/filebackend/FileBackend.php
@@ -1204,7 +1204,9 @@ abstract class FileBackend {
/**
* 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.
+ * This does not make use of the persistent file stat cache.
*
* @see FileBackend::getFileStat()
*
@@ -1491,7 +1493,7 @@ abstract class FileBackend {
* @ingroup FileBackend
* @since 1.23
*/
-class FileBackendException extends MWException {
+class FileBackendException extends Exception {
}
/**
diff --git a/includes/filebackend/FileBackendMultiWrite.php b/includes/filebackend/FileBackendMultiWrite.php
index bfffcc0f..f2d13eeb 100644
--- a/includes/filebackend/FileBackendMultiWrite.php
+++ b/includes/filebackend/FileBackendMultiWrite.php
@@ -299,7 +299,7 @@ class FileBackendMultiWrite extends FileBackend {
/**
* Check that a set of files are consistent across all internal backends
- * and re-synchronize those files againt the "multi master" if needed.
+ * and re-synchronize those files against the "multi master" if needed.
*
* @param array $paths List of storage paths
* @return Status
@@ -314,6 +314,8 @@ class FileBackendMultiWrite extends FileBackend {
$mStat = $mBackend->getFileStat( array( 'src' => $mPath, 'latest' => true ) );
if ( $mStat === null || ( $mSha1 !== false && !$mStat ) ) { // sanity
$status->fatal( 'backend-fail-internal', $this->name );
+ wfDebugLog( 'FileOperation', __METHOD__
+ . ': File is not available on the master backend' );
continue; // file is not available on the master backend...
}
// Check of all clone backends agree with the master...
@@ -326,6 +328,8 @@ class FileBackendMultiWrite extends FileBackend {
$cStat = $cBackend->getFileStat( array( 'src' => $cPath, 'latest' => true ) );
if ( $cStat === null || ( $cSha1 !== false && !$cStat ) ) { // sanity
$status->fatal( 'backend-fail-internal', $cBackend->getName() );
+ wfDebugLog( 'FileOperation', __METHOD__
+ . ': File is not available on the clone backend' );
continue; // file is not available on the clone backend...
}
if ( $mSha1 === $cSha1 ) {
diff --git a/includes/filebackend/FileBackendStore.php b/includes/filebackend/FileBackendStore.php
index 495ac3c0..25e87d43 100644
--- a/includes/filebackend/FileBackendStore.php
+++ b/includes/filebackend/FileBackendStore.php
@@ -118,7 +118,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function createInternal( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
@@ -159,7 +159,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function storeInternal( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
$status = Status::newFatal( 'backend-fail-maxsize',
$params['dst'], $this->maxFileSizeInternal() );
@@ -201,7 +201,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function copyInternal( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = $this->doCopyInternal( $params );
$this->clearCache( array( $params['dst'] ) );
if ( !isset( $params['dstExists'] ) || $params['dstExists'] ) {
@@ -233,7 +233,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function deleteInternal( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = $this->doDeleteInternal( $params );
$this->clearCache( array( $params['src'] ) );
$this->deleteFileCache( $params['src'] ); // persistent cache
@@ -267,7 +267,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function moveInternal( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = $this->doMoveInternal( $params );
$this->clearCache( array( $params['src'], $params['dst'] ) );
$this->deleteFileCache( $params['src'] ); // persistent cache
@@ -313,7 +313,7 @@ abstract class FileBackendStore extends FileBackend {
* @return Status
*/
final public function describeInternal( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
if ( count( $params['headers'] ) ) {
$status = $this->doDescribeInternal( $params );
$this->clearCache( array( $params['src'] ) );
@@ -346,7 +346,7 @@ abstract class FileBackendStore extends FileBackend {
}
final public function concatenate( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Try to lock the source files for the scope of this function
@@ -439,7 +439,7 @@ abstract class FileBackendStore extends FileBackend {
}
final protected function doPrepare( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
@@ -474,7 +474,7 @@ abstract class FileBackendStore extends FileBackend {
}
final protected function doSecure( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
@@ -509,7 +509,7 @@ abstract class FileBackendStore extends FileBackend {
}
final protected function doPublish( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
@@ -544,7 +544,7 @@ abstract class FileBackendStore extends FileBackend {
}
final protected function doClean( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Recursive: first delete all empty subdirs recursively
@@ -600,21 +600,21 @@ abstract class FileBackendStore extends FileBackend {
}
final public function fileExists( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __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}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
return $stat ? $stat['mtime'] : false;
}
final public function getFileSize( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$stat = $this->getFileStat( $params );
return $stat ? $stat['size'] : false;
@@ -625,9 +625,9 @@ abstract class FileBackendStore extends FileBackend {
if ( $path === null ) {
return false; // invalid storage path
}
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
- if ( !$this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
+ if ( !$latest && !$this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
$this->primeFileCache( array( $path ) ); // check persistent cache
}
if ( $this->cheapCache->has( $path, 'stat', self::CACHE_TTL ) ) {
@@ -644,9 +644,7 @@ abstract class FileBackendStore extends FileBackend {
}
}
}
- wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$stat = $this->doGetFileStat( $params );
- wfProfileOut( __METHOD__ . '-miss-' . $this->name );
if ( is_array( $stat ) ) { // file exists
// Strongly consistent backends can automatically set "latest"
$stat['latest'] = isset( $stat['latest'] ) ? $stat['latest'] : $latest;
@@ -679,7 +677,7 @@ abstract class FileBackendStore extends FileBackend {
abstract protected function doGetFileStat( array $params );
public function getFileContentsMulti( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
$contents = $this->doGetFileContentsMulti( $params );
@@ -708,7 +706,7 @@ abstract class FileBackendStore extends FileBackend {
if ( $path === null ) {
return false; // invalid storage path
}
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __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' );
@@ -718,12 +716,8 @@ abstract class FileBackendStore extends FileBackend {
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;
@@ -742,7 +736,7 @@ abstract class FileBackendStore extends FileBackend {
if ( $path === null ) {
return false; // invalid storage path
}
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$latest = !empty( $params['latest'] ); // use latest data?
if ( $this->cheapCache->has( $path, 'sha1', self::CACHE_TTL ) ) {
$stat = $this->cheapCache->get( $path, 'sha1' );
@@ -752,9 +746,7 @@ abstract class FileBackendStore extends FileBackend {
return $stat['hash'];
}
}
- wfProfileIn( __METHOD__ . '-miss-' . $this->name );
$hash = $this->doGetFileSha1Base36( $params );
- wfProfileOut( __METHOD__ . '-miss-' . $this->name );
$this->cheapCache->set( $path, 'sha1', array( 'hash' => $hash, 'latest' => $latest ) );
return $hash;
@@ -775,7 +767,7 @@ abstract class FileBackendStore extends FileBackend {
}
final public function getFileProps( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$fsFile = $this->getLocalReference( $params );
$props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
@@ -783,7 +775,7 @@ abstract class FileBackendStore extends FileBackend {
}
final public function getLocalReferenceMulti( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
@@ -826,7 +818,7 @@ abstract class FileBackendStore extends FileBackend {
}
final public function getLocalCopyMulti( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$params = $this->setConcurrencyFlags( $params );
$tmpFiles = $this->doGetLocalCopyMulti( $params );
@@ -851,7 +843,7 @@ abstract class FileBackendStore extends FileBackend {
}
final public function streamFile( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
$info = $this->getFileStat( $params );
@@ -865,9 +857,7 @@ 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-' . $this->name );
$status = $this->doStreamFile( $params );
- wfProfileOut( __METHOD__ . '-send-' . $this->name );
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
@@ -1071,7 +1061,7 @@ abstract class FileBackendStore extends FileBackend {
}
final protected function doOperationsInternal( array $ops, array $opts ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Fix up custom header name/value pairs...
@@ -1137,7 +1127,7 @@ abstract class FileBackendStore extends FileBackend {
}
final protected function doQuickOperationsInternal( array $ops ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$status = Status::newGood();
// Fix up custom header name/value pairs...
@@ -1198,13 +1188,13 @@ abstract class FileBackendStore extends FileBackend {
* The resulting Status object fields will correspond
* to the order in which the handles where given.
*
- * @param array $fileOpHandles
+ * @param FileBackendStoreOpHandle[] $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}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
foreach ( $fileOpHandles as $fileOpHandle ) {
if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
@@ -1223,9 +1213,11 @@ abstract class FileBackendStore extends FileBackend {
/**
* @see FileBackendStore::executeOpHandlesInternal()
- * @param array $fileOpHandles
+ *
+ * @param FileBackendStoreOpHandle[] $fileOpHandles
+ *
* @throws FileBackendError
- * @return array List of corresponding Status objects
+ * @return Status[] List of corresponding Status objects
*/
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
if ( count( $fileOpHandles ) ) {
@@ -1300,7 +1292,7 @@ abstract class FileBackendStore extends FileBackend {
}
final public function preloadFileStat( array $params ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$success = true; // no network errors
$params['concurrency'] = ( $this->parallelize !== 'off' ) ? $this->concurrency : 1;
@@ -1372,7 +1364,7 @@ abstract class FileBackendStore extends FileBackend {
/**
* Check if a container name is valid.
- * This checks for for length and illegal characters.
+ * This checks for length and illegal characters.
*
* @param string $container
* @return bool
@@ -1623,7 +1615,7 @@ abstract class FileBackendStore extends FileBackend {
* @param array $items
*/
final protected function primeContainerCache( array $items ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$paths = array(); // list of storage paths
$contNames = array(); // (cache key => resolved container name)
@@ -1733,7 +1725,7 @@ abstract class FileBackendStore extends FileBackend {
* @param array $items List of storage paths
*/
final protected function primeFileCache( array $items ) {
- $section = new ProfileSection( __METHOD__ . "-{$this->name}" );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$paths = array(); // list of storage paths
$pathNames = array(); // (cache key => storage path)
@@ -1755,17 +1747,18 @@ abstract class FileBackendStore extends FileBackend {
// Get all cache entries for these container cache keys...
$values = $this->memCache->getMulti( array_keys( $pathNames ) );
foreach ( $values as $cacheKey => $val ) {
+ $path = $pathNames[$cacheKey];
if ( is_array( $val ) ) {
- $path = $pathNames[$cacheKey];
+ $val['latest'] = false; // never completely trust cache
$this->cheapCache->set( $path, 'stat', $val );
if ( isset( $val['sha1'] ) ) { // some backends store SHA-1 as metadata
$this->cheapCache->set( $path, 'sha1',
- array( 'hash' => $val['sha1'], 'latest' => $val['latest'] ) );
+ array( 'hash' => $val['sha1'], 'latest' => false ) );
}
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'] ) );
+ array( 'map' => $val['xattr'], 'latest' => false ) );
}
}
}
diff --git a/includes/filebackend/FileOpBatch.php b/includes/filebackend/FileOpBatch.php
index b0d83e01..faa13144 100644
--- a/includes/filebackend/FileOpBatch.php
+++ b/includes/filebackend/FileOpBatch.php
@@ -55,7 +55,6 @@ class FileOpBatch {
* @return Status
*/
public static function attempt( array $performOps, array $opts, FileJournal $journal ) {
- $section = new ProfileSection( __METHOD__ );
$status = Status::newGood();
$n = count( $performOps );
diff --git a/includes/filebackend/SwiftFileBackend.php b/includes/filebackend/SwiftFileBackend.php
index f40ec46e..5f406c9b 100644
--- a/includes/filebackend/SwiftFileBackend.php
+++ b/includes/filebackend/SwiftFileBackend.php
@@ -112,7 +112,7 @@ class SwiftFileBackend extends FileBackendStore {
// Optional settings
$this->authTTL = isset( $config['swiftAuthTTL'] )
? $config['swiftAuthTTL']
- : 5 * 60; // some sane number
+ : 15 * 60; // some sane number
$this->swiftTempUrlKey = isset( $config['swiftTempUrlKey'] )
? $config['swiftTempUrlKey']
: '';
@@ -537,6 +537,7 @@ class SwiftFileBackend extends FileBackendStore {
return $status; // already there
} elseif ( $stat === null ) {
$status->fatal( 'backend-fail-internal', $this->name );
+ wfDebugLog( 'SwiftBackend', __METHOD__ . ': cannot get container stat' );
return $status;
}
@@ -568,6 +569,7 @@ class SwiftFileBackend extends FileBackendStore {
$status->fatal( 'backend-fail-usable', $params['dir'] );
} else {
$status->fatal( 'backend-fail-internal', $this->name );
+ wfDebugLog( 'SwiftBackend', __METHOD__ . ': cannot get container stat' );
}
return $status;
@@ -588,6 +590,7 @@ class SwiftFileBackend extends FileBackendStore {
$status->fatal( 'backend-fail-usable', $params['dir'] );
} else {
$status->fatal( 'backend-fail-internal', $this->name );
+ wfDebugLog( 'SwiftBackend', __METHOD__ . ': cannot get container stat' );
}
return $status;
@@ -607,6 +610,7 @@ class SwiftFileBackend extends FileBackendStore {
return $status; // ok, nothing to do
} elseif ( !is_array( $stat ) ) {
$status->fatal( 'backend-fail-internal', $this->name );
+ wfDebugLog( 'SwiftBackend', __METHOD__ . ': cannot get container stat' );
return $status;
}
@@ -643,7 +647,7 @@ class SwiftFileBackend extends FileBackendStore {
$timestamp = new MWTimestamp( $ts );
return $timestamp->getTimestamp( $format );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
throw new FileBackendError( $e->getMessage() );
}
}
@@ -660,7 +664,7 @@ class SwiftFileBackend extends FileBackendStore {
return $objHdrs; // nothing to do
}
- $section = new ProfileSection( __METHOD__ . '-' . $this->name );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
trigger_error( "$path was not stored with SHA-1 metadata.", E_USER_WARNING );
$auth = $this->getAuthentication();
@@ -794,7 +798,7 @@ class SwiftFileBackend extends FileBackendStore {
return $dirs; // nothing more
}
- $section = new ProfileSection( __METHOD__ . '-' . $this->name );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
// Non-recursive: only list dirs right under $dir
@@ -874,7 +878,7 @@ class SwiftFileBackend extends FileBackendStore {
return $files; // nothing more
}
- $section = new ProfileSection( __METHOD__ . '-' . $this->name );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
$prefix = ( $dir == '' ) ? null : "{$dir}/";
// $objects will contain a list of unfiltered names or CF_Object items
@@ -933,6 +937,7 @@ class SwiftFileBackend extends FileBackendStore {
// Convert various random Swift dates to TS_MW
'mtime' => $this->convertSwiftDate( $object->last_modified, TS_MW ),
'size' => (int)$object->bytes,
+ 'sha1' => null,
// Note: manifiest ETags are not an MD5 of the file
'md5' => ctype_xdigit( $object->hash ) ? $object->hash : null,
'latest' => false // eventually consistent
@@ -1060,6 +1065,7 @@ class SwiftFileBackend extends FileBackendStore {
$tmpFiles[$path] = $tmpFile;
}
+ $isLatest = ( $this->isRGW || !empty( $params['latest'] ) );
$opts = array( 'maxConnsPerHost' => $params['concurrency'] );
$reqs = $this->http->runMulti( $reqs, $opts );
foreach ( $reqs as $path => $op ) {
@@ -1074,6 +1080,10 @@ class SwiftFileBackend extends FileBackendStore {
$this->onError( null, __METHOD__,
array( 'src' => $path ) + $ep, $rerr, $rcode, $rdesc );
}
+ // Set the file stat process cache in passing
+ $stat = $this->getStatFromHeaders( $rhdrs );
+ $stat['latest'] = $isLatest;
+ $this->cheapCache->set( $path, 'stat', $stat );
} elseif ( $rcode === 404 ) {
$tmpFiles[$path] = false;
} else {
@@ -1161,6 +1171,11 @@ class SwiftFileBackend extends FileBackendStore {
return $hdrs;
}
+ /**
+ * @param FileBackendStoreOpHandle[] $fileOpHandles
+ *
+ * @return Status[]
+ */
protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
$statuses = array();
@@ -1253,6 +1268,7 @@ class SwiftFileBackend extends FileBackendStore {
if ( $rcode != 204 && $rcode !== 202 ) {
$status->fatal( 'backend-fail-internal', $this->name );
+ wfDebugLog( 'SwiftBackend', __METHOD__ . ': unexpected rcode value (' . $rcode . ')' );
}
return $status;
@@ -1267,7 +1283,7 @@ class SwiftFileBackend extends FileBackendStore {
* @return array|bool|null False on 404, null on failure
*/
protected function getContainerStat( $container, $bypassCache = false ) {
- $section = new ProfileSection( __METHOD__ . '-' . $this->name );
+ $ps = Profiler::instance()->scopedProfileIn( __METHOD__ . "-{$this->name}" );
if ( $bypassCache ) { // purge cache
$this->containerStatCache->clear( $container );
@@ -1280,13 +1296,11 @@ class SwiftFileBackend extends FileBackendStore {
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(
@@ -1507,25 +1521,8 @@ class SwiftFileBackend extends FileBackendStore {
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 )
- );
+ // Load the stat array from the headers
+ $stat = $this->getStatFromHeaders( $rhdrs );
if ( $this->isRGW ) {
$stat['latest'] = true; // strong consistency
}
@@ -1542,6 +1539,34 @@ class SwiftFileBackend extends FileBackendStore {
}
/**
+ * @param array $rhdrs
+ * @return array
+ */
+ protected function getStatFromHeaders( array $rhdrs ) {
+ // 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 ) );
+ return 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' => isset( $rhdrs['x-object-meta-sha1base36'] )
+ ? $rhdrs['x-object-meta-sha1base36']
+ : null,
+ // Note: manifiest ETags are not an MD5 of the file
+ 'md5' => ctype_xdigit( $rhdrs['etag'] ) ? $rhdrs['etag'] : null,
+ 'xattr' => array( 'metadata' => $metadata, 'headers' => $headers )
+ );
+ }
+
+ /**
* @return array|null Credential map
*/
protected function getAuthentication() {
@@ -1595,7 +1620,7 @@ class SwiftFileBackend extends FileBackendStore {
}
// 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
+ $this->isRGW = true; // take advantage of strong consistency in Ceph
}
}
diff --git a/includes/filebackend/TempFSFile.php b/includes/filebackend/TempFSFile.php
index 1b68130f..791be7fc 100644
--- a/includes/filebackend/TempFSFile.php
+++ b/includes/filebackend/TempFSFile.php
@@ -55,7 +55,6 @@ class TempFSFile extends FSFile {
* @return TempFSFile|null
*/
public static function factory( $prefix, $extension = '' ) {
- wfProfileIn( __METHOD__ );
$base = wfTempDir() . '/' . $prefix . wfRandomString( 12 );
$ext = ( $extension != '' ) ? ".{$extension}" : "";
for ( $attempt = 1; true; $attempt++ ) {
@@ -68,14 +67,12 @@ class TempFSFile extends FSFile {
break; // got it
}
if ( $attempt >= 5 ) {
- wfProfileOut( __METHOD__ );
return null; // give up
}
}
$tmpFile = new self( $path );
$tmpFile->autocollect(); // safely instantiated
- wfProfileOut( __METHOD__ );
return $tmpFile;
}
diff --git a/includes/filebackend/filejournal/FileJournal.php b/includes/filebackend/filejournal/FileJournal.php
index c0651485..4ee52220 100644
--- a/includes/filebackend/filejournal/FileJournal.php
+++ b/includes/filebackend/filejournal/FileJournal.php
@@ -57,14 +57,14 @@ abstract class FileJournal {
*
* @param array $config
* @param string $backend A registered file backend name
- * @throws MWException
+ * @throws Exception
* @return FileJournal
*/
final public static function factory( array $config, $backend ) {
$class = $config['class'];
$jrn = new $class( $config );
if ( !$jrn instanceof self ) {
- throw new MWException( "Class given is not an instance of FileJournal." );
+ throw new Exception( "Class given is not an instance of FileJournal." );
}
$jrn->backend = $backend;
diff --git a/includes/filebackend/lockmanager/DBLockManager.php b/includes/filebackend/lockmanager/DBLockManager.php
index 450ccc82..39a55635 100644
--- a/includes/filebackend/lockmanager/DBLockManager.php
+++ b/includes/filebackend/lockmanager/DBLockManager.php
@@ -52,7 +52,7 @@ abstract class DBLockManager extends QuorumLockManager {
/**
* Construct a new instance from configuration.
*
- * @param array $config Paramaters include:
+ * @param array $config Parameters include:
* - dbServers : Associative array of DB names to server configuration.
* Configuration is an associative array that includes:
* - host : DB server name
@@ -97,7 +97,7 @@ abstract class DBLockManager extends QuorumLockManager {
// connection timeouts. This is useless if each bucket has one peer.
try {
$this->statusCache = ObjectCache::newAccelerator( array() );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
trigger_error( __CLASS__ .
" using multiple DB peers without apc, xcache, or wincache." );
}
diff --git a/includes/filebackend/lockmanager/LockManager.php b/includes/filebackend/lockmanager/LockManager.php
index df8d2d4f..615ba77e 100644
--- a/includes/filebackend/lockmanager/LockManager.php
+++ b/includes/filebackend/lockmanager/LockManager.php
@@ -64,7 +64,7 @@ abstract class LockManager {
/**
* Construct a new instance from configuration
*
- * @param array $config Paramaters include:
+ * @param array $config Parameters 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.
@@ -72,9 +72,9 @@ abstract class LockManager {
public function __construct( array $config ) {
$this->domain = isset( $config['domain'] ) ? $config['domain'] : wfWikiID();
if ( isset( $config['lockTTL'] ) ) {
- $this->lockTTL = max( 1, $config['lockTTL'] );
+ $this->lockTTL = max( 5, $config['lockTTL'] );
} elseif ( PHP_SAPI === 'cli' ) {
- $this->lockTTL = 2 * 3600;
+ $this->lockTTL = 3600;
} else {
$met = ini_get( 'max_execution_time' ); // this is 0 in CLI mode
$this->lockTTL = max( 5 * 60, 2 * (int)$met );
@@ -102,7 +102,6 @@ abstract class LockManager {
* @since 1.22
*/
final public function lockByType( array $pathsByType, $timeout = 0 ) {
- wfProfileIn( __METHOD__ );
$status = Status::newGood();
$pathsByType = $this->normalizePathsByType( $pathsByType );
$msleep = array( 0, 50, 100, 300, 500 ); // retry backoff times
@@ -116,7 +115,6 @@ abstract class LockManager {
usleep( 1e3 * ( next( $msleep ) ?: 1000 ) ); // use 1 sec after enough times
$elapsed = microtime( true ) - $start;
} while ( $elapsed < $timeout && $elapsed >= 0 );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -140,10 +138,8 @@ abstract class LockManager {
* @since 1.22
*/
final public function unlockByType( array $pathsByType ) {
- wfProfileIn( __METHOD__ );
$pathsByType = $this->normalizePathsByType( $pathsByType );
$status = $this->doUnlockByType( $pathsByType );
- wfProfileOut( __METHOD__ );
return $status;
}
diff --git a/includes/filebackend/lockmanager/LockManagerGroup.php b/includes/filebackend/lockmanager/LockManagerGroup.php
index 19fc4fef..c72863ed 100644
--- a/includes/filebackend/lockmanager/LockManagerGroup.php
+++ b/includes/filebackend/lockmanager/LockManagerGroup.php
@@ -78,17 +78,17 @@ class LockManagerGroup {
* Register an array of file lock manager configurations
*
* @param array $configs
- * @throws MWException
+ * @throws Exception
*/
protected function register( array $configs ) {
foreach ( $configs as $config ) {
$config['domain'] = $this->domain;
if ( !isset( $config['name'] ) ) {
- throw new MWException( "Cannot register a lock manager with no name." );
+ throw new Exception( "Cannot register a lock manager with no name." );
}
$name = $config['name'];
if ( !isset( $config['class'] ) ) {
- throw new MWException( "Cannot register lock manager `{$name}` with no class." );
+ throw new Exception( "Cannot register lock manager `{$name}` with no class." );
}
$class = $config['class'];
unset( $config['class'] ); // lock manager won't need this
@@ -105,11 +105,11 @@ class LockManagerGroup {
*
* @param string $name
* @return LockManager
- * @throws MWException
+ * @throws Exception
*/
public function get( $name ) {
if ( !isset( $this->managers[$name] ) ) {
- throw new MWException( "No lock manager defined with the name `$name`." );
+ throw new Exception( "No lock manager defined with the name `$name`." );
}
// Lazy-load the actual lock manager instance
if ( !isset( $this->managers[$name]['instance'] ) ) {
@@ -126,11 +126,11 @@ class LockManagerGroup {
*
* @param string $name
* @return array
- * @throws MWException
+ * @throws Exception
*/
public function config( $name ) {
if ( !isset( $this->managers[$name] ) ) {
- throw new MWException( "No lock manager defined with the name `$name`." );
+ throw new Exception( "No lock manager defined with the name `$name`." );
}
$class = $this->managers[$name]['class'];
@@ -155,7 +155,7 @@ class LockManagerGroup {
* Throws an exception if no lock manager could be found.
*
* @return LockManager
- * @throws MWException
+ * @throws Exception
*/
public function getAny() {
return isset( $this->managers['default'] )
diff --git a/includes/filebackend/lockmanager/MemcLockManager.php b/includes/filebackend/lockmanager/MemcLockManager.php
index 9bb01c21..24d96e02 100644
--- a/includes/filebackend/lockmanager/MemcLockManager.php
+++ b/includes/filebackend/lockmanager/MemcLockManager.php
@@ -55,13 +55,13 @@ class MemcLockManager extends QuorumLockManager {
/**
* Construct a new instance from configuration.
*
- * @param array $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.
* - memcConfig : Configuration array for ObjectCache::newFromParams. [optional]
* If set, this must use one of the memcached classes.
- * @throws MWException
+ * @throws Exception
*/
public function __construct( array $config ) {
parent::__construct( $config );
@@ -80,7 +80,7 @@ class MemcLockManager extends QuorumLockManager {
if ( $cache instanceof MemcachedBagOStuff ) {
$this->bagOStuffs[$name] = $cache;
} else {
- throw new MWException(
+ throw new Exception(
'Only MemcachedBagOStuff classes are supported by MemcLockManager.' );
}
}
diff --git a/includes/filebackend/lockmanager/RedisLockManager.php b/includes/filebackend/lockmanager/RedisLockManager.php
index 90e05817..90e62e69 100644
--- a/includes/filebackend/lockmanager/RedisLockManager.php
+++ b/includes/filebackend/lockmanager/RedisLockManager.php
@@ -62,7 +62,7 @@ class RedisLockManager extends QuorumLockManager {
* - 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().
- * @throws MWException
+ * @throws Exception
*/
public function __construct( array $config ) {
parent::__construct( $config );
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index 59295257..5b42c2c6 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -114,6 +114,9 @@ class FileRepo {
/** @var string The URL of the repo's favicon, if any */
protected $favicon;
+ /** @var bool Whether all zones should be private (e.g. private wiki repo) */
+ protected $isPrivate;
+
/**
* Factory functions for creating new files
* Override these in the base class
@@ -269,7 +272,7 @@ class FileRepo {
* @return string|bool
*/
public function getZoneUrl( $zone, $ext = null ) {
- if ( in_array( $zone, array( 'public', 'temp', 'thumb', 'transcoded' ) ) ) {
+ if ( in_array( $zone, array( 'public', 'thumb', 'transcoded' ) ) ) {
// standard public zones
if ( $ext !== null && isset( $this->zones[$zone]['urlsByExt'][$ext] ) ) {
// custom URL for extension/zone
@@ -283,7 +286,6 @@ class FileRepo {
case 'public':
return $this->url;
case 'temp':
- return "{$this->url}/temp";
case 'deleted':
return false; // no public URL
case 'thumb':
@@ -404,6 +406,7 @@ class FileRepo {
* 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.
+ * latest: If true, load from the latest available data into File objects
* @return File|bool False on failure
*/
public function findFile( $title, $options = array() ) {
@@ -411,18 +414,24 @@ class FileRepo {
if ( !$title ) {
return false;
}
+ if ( isset( $options['bypassCache'] ) ) {
+ $options['latest'] = $options['bypassCache']; // b/c
+ }
$time = isset( $options['time'] ) ? $options['time'] : false;
+ $flags = !empty( $options['latest'] ) ? File::READ_LATEST : 0;
# First try the current version of the file to see if it precedes the timestamp
$img = $this->newFile( $title );
if ( !$img ) {
return false;
}
+ $img->load( $flags );
if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
return $img;
}
# Now try an old version of the file
if ( $time !== false ) {
$img = $this->newFile( $title, $time );
+ $img->load( $flags );
if ( $img && $img->exists() ) {
if ( !$img->isDeleted( File::DELETED_FILE ) ) {
return $img; // always OK
@@ -443,6 +452,7 @@ class FileRepo {
$redir = $this->checkRedirect( $title );
if ( $redir && $title->getNamespace() == NS_FILE ) {
$img = $this->newFile( $redir );
+ $img->load( $flags );
if ( !$img ) {
return false;
}
@@ -1305,7 +1315,10 @@ class FileRepo {
list( , $container, ) = FileBackend::splitStoragePath( $path );
$params = array( 'dir' => $path );
- if ( $this->isPrivate || $container === $this->zones['deleted']['container'] ) {
+ if ( $this->isPrivate
+ || $container === $this->zones['deleted']['container']
+ || $container === $this->zones['temp']['container']
+ ) {
# Take all available measures to prevent web accessibility of new deleted
# directories, in case the user has not configured offline storage
$params = array( 'noAccess' => true, 'noListing' => true ) + $params;
@@ -1676,23 +1689,26 @@ class FileRepo {
* Create a new fatal error
*
* @param string $message
- * @return FileRepoStatus
+ * @return Status
*/
public function newFatal( $message /*, parameters...*/ ) {
- $params = func_get_args();
- array_unshift( $params, $this );
+ $status = call_user_func_array( array( 'Status', 'newFatal' ), func_get_args() );
+ $status->cleanCallback = $this->getErrorCleanupFunction();
- return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
+ return $status;
}
/**
* Create a new good result
*
* @param null|string $value
- * @return FileRepoStatus
+ * @return Status
*/
public function newGood( $value = null ) {
- return FileRepoStatus::newGood( $this, $value );
+ $status = Status::newGood( $value );
+ $status->cleanCallback = $this->getErrorCleanupFunction();
+
+ return $status;
}
/**
@@ -1785,9 +1801,9 @@ class FileRepo {
}
/**
- * Get an temporary FileRepo associated with this repo.
- * Files will be created in the temp zone of this repo and
- * thumbnails in a /temp subdirectory in thumb zone of this repo.
+ * Get a temporary private FileRepo associated with this repo.
+ *
+ * Files will be created in the temp zone of this repo.
* It will have the same backend as this repo.
*
* @return TempFileRepo
@@ -1798,26 +1814,26 @@ class FileRepo {
'backend' => $this->backend,
'zones' => array(
'public' => array(
+ // Same place storeTemp() uses in the base repo, though
+ // the path hashing is mismatched, which is annoying.
'container' => $this->zones['temp']['container'],
'directory' => $this->zones['temp']['directory']
),
'thumb' => array(
- 'container' => $this->zones['thumb']['container'],
- 'directory' => $this->zones['thumb']['directory'] == ''
- ? 'temp'
- : $this->zones['thumb']['directory'] . '/temp'
+ 'container' => $this->zones['temp']['container'],
+ 'directory' => $this->zones['temp']['directory'] == ''
+ ? 'thumb'
+ : $this->zones['temp']['directory'] . '/thumb'
),
'transcoded' => array(
- 'container' => $this->zones['transcoded']['container'],
- 'directory' => $this->zones['transcoded']['directory'] == ''
- ? 'temp'
- : $this->zones['transcoded']['directory'] . '/temp'
+ 'container' => $this->zones['temp']['container'],
+ 'directory' => $this->zones['temp']['directory'] == ''
+ ? 'transcoded'
+ : $this->zones['temp']['directory'] . '/transcoded'
)
),
- 'url' => $this->getZoneUrl( 'temp' ),
- 'thumbUrl' => $this->getZoneUrl( 'thumb' ) . '/temp',
- 'transcodedUrl' => $this->getZoneUrl( 'transcoded' ) . '/temp',
- 'hashLevels' => $this->hashLevels // performance
+ 'hashLevels' => $this->hashLevels, // performance
+ 'isPrivate' => true // all in temp zone
) );
}
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 56848df2..67080b6f 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -24,41 +24,7 @@
/**
* Generic operation result class for FileRepo-related operations
* @ingroup FileRepo
+ * @deprecated 1.25
*/
class FileRepoStatus extends Status {
- /**
- * Factory function for fatal errors
- *
- * @param FileRepo $repo
- * @return FileRepoStatus
- */
- static function newFatal( $repo /*, parameters...*/ ) {
- $params = array_slice( func_get_args(), 1 );
- $result = new self( $repo );
- call_user_func_array( array( &$result, 'error' ), $params );
- $result->ok = false;
-
- return $result;
- }
-
- /**
- * @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 bool|FileRepo $repo
- */
- function __construct( $repo = false ) {
- if ( $repo ) {
- $this->cleanCallback = $repo->getErrorCleanupFunction();
- }
- }
}
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 6924f0a6..7ead968f 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -514,7 +514,7 @@ class ForeignAPIRepo extends FileRepo {
$options['timeout'] = 'default';
}
- $req = MWHttpRequest::factory( $url, $options );
+ $req = MWHttpRequest::factory( $url, $options, __METHOD__ );
$req->setUserAgent( ForeignAPIRepo::getUserAgent() );
$status = $req->execute();
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index fab42162..050c4290 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -114,7 +114,7 @@ class RepoGroup {
* 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
+ * latest: If true, load from the latest available data into File objects
* @return File|bool False if title is not found
*/
function findFile( $title, $options = array() ) {
@@ -122,6 +122,10 @@ class RepoGroup {
// MW 1.15 compat
$options = array( 'time' => $options );
}
+ if ( isset( $options['bypassCache'] ) ) {
+ $options['latest'] = $options['bypassCache']; // b/c
+ }
+
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
diff --git a/includes/filerepo/file/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php
index 5b0d8e2b..1d454283 100644
--- a/includes/filerepo/file/ArchivedFile.php
+++ b/includes/filerepo/file/ArchivedFile.php
@@ -100,8 +100,9 @@ class ArchivedFile {
* @param Title $title
* @param int $id
* @param string $key
+ * @param string $sha1
*/
- function __construct( $title, $id = 0, $key = '' ) {
+ function __construct( $title, $id = 0, $key = '', $sha1 = '' ) {
$this->id = -1;
$this->title = false;
$this->name = false;
@@ -136,7 +137,11 @@ class ArchivedFile {
$this->key = $key;
}
- if ( !$id && !$key && !( $title instanceof Title ) ) {
+ if ( $sha1 ) {
+ $this->sha1 = $sha1;
+ }
+
+ if ( !$id && !$key && !( $title instanceof Title ) && !$sha1 ) {
throw new MWException( "No specifications provided to ArchivedFile constructor." );
}
}
@@ -162,6 +167,9 @@ class ArchivedFile {
if ( $this->title ) {
$conds['fa_name'] = $this->title->getDBkey();
}
+ if ( $this->sha1 ) {
+ $conds['fa_sha1'] = $this->sha1;
+ }
if ( !count( $conds ) ) {
throw new MWException( "No specific information for retrieving archived file" );
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php
index b574c5e7..6edd6fcc 100644
--- a/includes/filerepo/file/File.php
+++ b/includes/filerepo/file/File.php
@@ -47,7 +47,7 @@
*
* @ingroup FileAbstraction
*/
-abstract class File {
+abstract class File implements IDBAccessObject {
// Bitfield values akin to the Revision deletion constants
const DELETED_FILE = 1;
const DELETED_COMMENT = 2;
@@ -138,10 +138,10 @@ abstract class File {
/** @var Title */
protected $redirectTitle;
- /** @var bool Wether the output of transform() for this file is likely to be valid. */
+ /** @var bool Whether the output of transform() for this file is likely to be valid. */
protected $canRender;
- /** @var bool Wether this media file is in a format that is unlikely to
+ /** @var bool Whether this media file is in a format that is unlikely to
* contain viruses or malicious content
*/
protected $isSafeFile;
@@ -490,7 +490,7 @@ abstract class File {
sort( $sortedBuckets );
foreach ( $sortedBuckets as $bucket ) {
- if ( $bucket > $imageWidth ) {
+ if ( $bucket >= $imageWidth ) {
return false;
}
@@ -837,6 +837,18 @@ abstract class File {
}
/**
+ * Load any lazy-loaded file object fields from source
+ *
+ * This is only useful when setting $flags
+ *
+ * Overridden by LocalFile to actually query the DB
+ *
+ * @param integer $flags Bitfield of File::READ_* constants
+ */
+ public function load( $flags = 0 ) {
+ }
+
+ /**
* Returns true if file exists in the repository.
*
* Overridden by LocalFile to avoid unnecessary stat calls.
@@ -996,7 +1008,6 @@ abstract class File {
function transform( $params, $flags = 0 ) {
global $wgThumbnailEpoch;
- wfProfileIn( __METHOD__ );
do {
if ( !$this->canRender() ) {
$thumb = $this->iconThumb();
@@ -1069,8 +1080,6 @@ abstract class File {
}
} while ( false );
- wfProfileOut( __METHOD__ );
-
return is_object( $thumb ) ? $thumb : false;
}
@@ -1100,9 +1109,7 @@ abstract class File {
}
// 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?
@@ -1123,7 +1130,7 @@ abstract class File {
$thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
}
// Give extensions a chance to do something with this thumbnail...
- wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
+ Hooks::run( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
}
// Purge. Useful in the event of Core -> Squid connection failure or squid
@@ -1237,7 +1244,7 @@ abstract class File {
$that = $this;
$work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
array(
- 'doWork' => function() use ( $that ) {
+ 'doWork' => function () use ( $that ) {
return $that->getLocalRefPath();
}
)
@@ -1458,7 +1465,7 @@ abstract class File {
/**
* Get the path of the file relative to the public zone root.
- * This function is overriden in OldLocalFile to be like getArchiveRel().
+ * This function is overridden in OldLocalFile to be like getArchiveRel().
*
* @return string
*/
@@ -1502,7 +1509,7 @@ abstract class File {
/**
* Get urlencoded path of the file relative to the public zone root.
- * This function is overriden in OldLocalFile to be like getArchiveUrl().
+ * This function is overridden in OldLocalFile to be like getArchiveUrl().
*
* @return string
*/
@@ -1770,14 +1777,15 @@ abstract class File {
}
/**
+ * @param bool|IContextSource $context Context to use (optional)
* @return bool
*/
- function formatMetadata() {
+ function formatMetadata( $context = false ) {
if ( !$this->getHandler() ) {
return false;
}
- return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
+ return $this->getHandler()->formatMetadata( $this, $context );
}
/**
@@ -2013,7 +2021,7 @@ abstract class File {
wfDebug( "miss\n" );
}
wfDebug( "Fetching shared description from $renderUrl\n" );
- $res = Http::get( $renderUrl );
+ $res = Http::get( $renderUrl, array(), __METHOD__ );
if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
$wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
}
@@ -2052,6 +2060,17 @@ abstract class File {
}
/**
+ * Returns the timestamp (in TS_MW format) of the last change of the description page.
+ * Returns false if the file does not have a description page, or retrieving the timestamp
+ * would be expensive.
+ * @since 1.25
+ * @return string|bool
+ */
+ public function getDescriptionTouched() {
+ return false;
+ }
+
+ /**
* Get the SHA-1 base 36 hash of the file
*
* @return string
diff --git a/includes/filerepo/file/LocalFile.php b/includes/filerepo/file/LocalFile.php
index 8824b669..b4cced38 100644
--- a/includes/filerepo/file/LocalFile.php
+++ b/includes/filerepo/file/LocalFile.php
@@ -109,6 +109,9 @@ class LocalFile extends File {
/** @var string Description of current revision of the file */
private $description;
+ /** @var string TS_MW timestamp of the last change of the file description */
+ private $descriptionTouched;
+
/** @var bool Whether the row was upgraded on load */
private $upgraded;
@@ -121,13 +124,8 @@ class LocalFile extends File {
/** @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
+ // @note: higher than IDBAccessObject constants
+ const LOAD_ALL = 16; // integer; load all the lazy fields too (like metadata)
/**
* Create a LocalFile from a title
@@ -247,13 +245,11 @@ class LocalFile extends File {
function loadFromCache() {
global $wgMemc;
- wfProfileIn( __METHOD__ );
$this->dataLoaded = false;
$this->extraDataLoaded = false;
$key = $this->getCacheKey();
if ( !$key ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -280,8 +276,6 @@ class LocalFile extends File {
wfIncrStats( 'image_cache_miss' );
}
- wfProfileOut( __METHOD__ );
-
return $this->dataLoaded;
}
@@ -382,17 +376,15 @@ class LocalFile extends File {
* @param int $flags
*/
function loadFromDB( $flags = 0 ) {
- # Polymorphic function name to distinguish foreign and local fetches
$fname = get_class( $this ) . '::' . __FUNCTION__;
- wfProfileIn( $fname );
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
$this->dataLoaded = true;
$this->extraDataLoaded = true;
- $dbr = ( $flags & self::LOAD_VIA_SLAVE )
- ? $this->repo->getSlaveDB()
- : $this->repo->getMasterDB();
+ $dbr = ( $flags & self::READ_LATEST )
+ ? $this->repo->getMasterDB()
+ : $this->repo->getSlaveDB();
$row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
array( 'img_name' => $this->getName() ), $fname );
@@ -402,8 +394,6 @@ class LocalFile extends File {
} else {
$this->fileExists = false;
}
-
- wfProfileOut( $fname );
}
/**
@@ -411,9 +401,7 @@ class LocalFile extends File {
* This covers fields that are sometimes not cached.
*/
protected function loadExtraFromDB() {
- # Polymorphic function name to distinguish foreign and local fetches
$fname = get_class( $this ) . '::' . __FUNCTION__;
- wfProfileIn( $fname );
# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
$this->extraDataLoaded = true;
@@ -428,11 +416,8 @@ class LocalFile extends File {
$this->$name = $value;
}
} else {
- wfProfileOut( $fname );
throw new MWException( "Could not find data for image '{$this->getName()}'." );
}
-
- wfProfileOut( $fname );
}
/**
@@ -540,13 +525,14 @@ class LocalFile extends File {
*/
function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
- if ( !$this->loadFromCache() ) {
- $this->loadFromDB( $this->isVolatile() ? 0 : self::LOAD_VIA_SLAVE );
+ if ( ( $flags & self::READ_LATEST ) || !$this->loadFromCache() ) {
+ $this->loadFromDB( $flags );
$this->saveToCache();
}
$this->dataLoaded = true;
}
if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
+ // @note: loads on name/timestamp to reduce race condition problems
$this->loadExtraFromDB();
}
}
@@ -587,7 +573,6 @@ class LocalFile extends File {
* Fix assorted version-related problems with the image row by reloading it from the file
*/
function upgradeRow() {
- wfProfileIn( __METHOD__ );
$this->lock(); // begin
@@ -597,7 +582,6 @@ class LocalFile extends File {
if ( !$this->fileExists ) {
$this->unlock();
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
- wfProfileOut( __METHOD__ );
return;
}
@@ -607,7 +591,6 @@ class LocalFile extends File {
if ( wfReadOnly() ) {
$this->unlock();
- wfProfileOut( __METHOD__ );
return;
}
@@ -633,7 +616,6 @@ class LocalFile extends File {
$this->unlock(); // done
- wfProfileOut( __METHOD__ );
}
/**
@@ -672,7 +654,7 @@ class LocalFile extends File {
/** getURL inherited */
/** getViewURL inherited */
/** getPath inherited */
- /** isVisible inhereted */
+ /** isVisible inherited */
/**
* @return bool
@@ -860,7 +842,7 @@ class LocalFile extends File {
* Refresh metadata in memcached, but don't touch thumbnails or squid
*/
function purgeMetadataCache() {
- $this->loadFromDB();
+ $this->loadFromDB( File::READ_LATEST );
$this->saveToCache();
$this->purgeHistory();
}
@@ -889,7 +871,6 @@ class LocalFile extends File {
* @note This used to purge old thumbnails by default as well, but doesn't anymore.
*/
function purgeCache( $options = array() ) {
- wfProfileIn( __METHOD__ );
// Refresh metadata cache
$this->purgeMetadataCache();
@@ -898,7 +879,6 @@ class LocalFile extends File {
// Purge squid cache for this file
SquidUpdate::purge( array( $this->getURL() ) );
- wfProfileOut( __METHOD__ );
}
/**
@@ -907,13 +887,12 @@ class LocalFile extends File {
*/
function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
- wfProfileIn( __METHOD__ );
// Get a list of old thumbnails and URLs
$files = $this->getThumbnails( $archiveName );
// Purge any custom thumbnail caches
- wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
+ Hooks::run( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
$dir = array_shift( $files );
$this->purgeThumbList( $dir, $files );
@@ -927,7 +906,6 @@ class LocalFile extends File {
SquidUpdate::purge( $urls );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -936,7 +914,6 @@ class LocalFile extends File {
*/
function purgeThumbnails( $options = array() ) {
global $wgUseSquid;
- wfProfileIn( __METHOD__ );
// Delete thumbnails
$files = $this->getThumbnails();
@@ -958,7 +935,7 @@ class LocalFile extends File {
}
// Purge any custom thumbnail caches
- wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, false ) );
+ Hooks::run( 'LocalFilePurgeThumbnails', array( $this, false ) );
$dir = array_shift( $files );
$this->purgeThumbList( $dir, $files );
@@ -968,7 +945,6 @@ class LocalFile extends File {
SquidUpdate::purge( $urls );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -1035,7 +1011,7 @@ class LocalFile extends File {
$opts['ORDER BY'] = "oi_timestamp $order";
$opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' );
- wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
+ Hooks::run( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
&$conds, &$opts, &$join_conds ) );
$res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
@@ -1146,7 +1122,6 @@ class LocalFile extends File {
}
if ( !$props ) {
- wfProfileIn( __METHOD__ . '-getProps' );
if ( $this->repo->isVirtualUrl( $srcPath )
|| FileBackend::isStoragePath( $srcPath )
) {
@@ -1154,7 +1129,6 @@ class LocalFile extends File {
} else {
$props = FSFile::getPropsFromPath( $srcPath );
}
- wfProfileOut( __METHOD__ . '-getProps' );
}
$options = array();
@@ -1236,7 +1210,6 @@ class LocalFile extends File {
function recordUpload2( $oldver, $comment, $pageText, $props = false, $timestamp = false,
$user = null
) {
- wfProfileIn( __METHOD__ );
if ( is_null( $user ) ) {
global $wgUser;
@@ -1247,9 +1220,7 @@ class LocalFile extends File {
$dbw->begin( __METHOD__ );
if ( !$props ) {
- wfProfileIn( __METHOD__ . '-getProps' );
$props = $this->repo->getFileProps( $this->getVirtualUrl() );
- wfProfileOut( __METHOD__ . '-getProps' );
}
# Imports or such might force a certain timestamp; otherwise we generate
@@ -1271,7 +1242,6 @@ class LocalFile extends File {
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return false;
}
@@ -1303,9 +1273,11 @@ class LocalFile extends File {
);
if ( $dbw->affectedRows() == 0 ) {
if ( $allowTimeKludge ) {
- # Use FOR UPDATE to ignore any transaction snapshotting
+ # Use LOCK IN SHARE MODE to ignore any transaction snapshotting
$ltimestamp = $dbw->selectField( 'image', 'img_timestamp',
- array( 'img_name' => $this->getName() ), __METHOD__, array( 'FOR UPDATE' ) );
+ array( 'img_name' => $this->getName() ),
+ __METHOD__,
+ array( 'LOCK IN SHARE MODE' ) );
$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
@@ -1404,12 +1376,14 @@ class LocalFile extends File {
// Page exists, do RC entry now (otherwise we wait for later).
$logEntry->publish( $logId );
}
- wfProfileIn( __METHOD__ . '-edit' );
if ( $exists ) {
# Create a null revision
$latest = $descTitle->getLatestRevID();
- $editSummary = LogFormatter::newFromEntry( $logEntry )->getPlainActionText();
+ // Use own context to get the action text in content language
+ $formatter = LogFormatter::newFromEntry( $logEntry );
+ $formatter->setContext( RequestContext::newExtraneousContext( $descTitle ) );
+ $editSummary = $formatter->getPlainActionText();
$nullRevision = Revision::newNullRevision(
$dbw,
@@ -1421,7 +1395,7 @@ class LocalFile extends File {
if ( !is_null( $nullRevision ) ) {
$nullRevision->insertOn( $dbw );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) );
$wikiPage->updateRevisionOn( $dbw, $nullRevision );
}
}
@@ -1468,22 +1442,16 @@ class LocalFile extends File {
$dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
- wfProfileOut( __METHOD__ . '-edit' );
-
if ( $reupload ) {
# Delete old thumbnails
- wfProfileIn( __METHOD__ . '-purge' );
$this->purgeThumbnails();
- wfProfileOut( __METHOD__ . '-purge' );
# Remove the old file from the squid cache
SquidUpdate::purge( array( $this->getURL() ) );
}
# Hooks, hooks, the magic of hooks...
- wfProfileIn( __METHOD__ . '-hooks' );
- wfRunHooks( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
- wfProfileOut( __METHOD__ . '-hooks' );
+ Hooks::run( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
# Invalidate cache for all pages using this file
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
@@ -1492,8 +1460,6 @@ class LocalFile extends File {
LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
}
- wfProfileOut( __METHOD__ );
-
return true;
}
@@ -1811,6 +1777,25 @@ class LocalFile extends File {
}
/**
+ * @return bool|string
+ */
+ public function getDescriptionTouched() {
+ // The DB lookup might return false, e.g. if the file was just deleted, or the shared DB repo
+ // itself gets it from elsewhere. To avoid repeating the DB lookups in such a case, we
+ // need to differentiate between null (uninitialized) and false (failed to load).
+ if ( $this->descriptionTouched === null ) {
+ $cond = array(
+ 'page_namespace' => $this->title->getNamespace(),
+ 'page_title' => $this->title->getDBkey()
+ );
+ $touched = $this->repo->getSlaveDB()->selectField( 'page', 'page_touched', $cond, __METHOD__ );
+ $this->descriptionTouched = $touched ? wfTimestamp( TS_MW, $touched ) : false;
+ }
+
+ return $this->descriptionTouched;
+ }
+
+ /**
* @return string
*/
function getSha1() {
@@ -1850,7 +1835,7 @@ class LocalFile extends File {
* Start a transaction and lock the image for update
* Increments a reference counter if the lock is already held
* @throws MWException Throws an error if the lock was not acquired
- * @return bool Success
+ * @return bool Whether the file lock owns/spawned the DB transaction
*/
function lock() {
$dbw = $this->repo->getMasterDB();
@@ -1875,9 +1860,7 @@ class LocalFile extends File {
} );
}
- $this->markVolatile(); // file may change soon
-
- return true;
+ return $this->lockedOwnTrx;
}
/**
@@ -1896,48 +1879,6 @@ 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() {
@@ -1985,7 +1926,7 @@ class LocalFileDeleteBatch {
/** @var array Items to be processed in the deletion batch */
private $deletionBatch;
- /** @var bool Wether to suppress all suppressable fields when deleting */
+ /** @var bool Whether to suppress all suppressable fields when deleting */
private $suppress;
/** @var FileRepoStatus */
@@ -2238,26 +2179,9 @@ class LocalFileDeleteBatch {
* @return FileRepoStatus
*/
function execute() {
- wfProfileIn( __METHOD__ );
$this->file->lock();
- // Leave private files alone
- $privateFiles = array();
- list( $oldRels, ) = $this->getOldRels();
- $dbw = $this->file->repo->getMasterDB();
-
- if ( !empty( $oldRels ) ) {
- $res = $dbw->select( 'oldimage',
- array( 'oi_archive_name' ),
- array( 'oi_name' => $this->file->getName(),
- 'oi_archive_name' => array_keys( $oldRels ),
- $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
- __METHOD__ );
- foreach ( $res as $row ) {
- $privateFiles[$row->oi_archive_name] = 1;
- }
- }
// Prepare deletion batch
$hashes = $this->getHashes();
$this->deletionBatch = array();
@@ -2265,9 +2189,8 @@ class LocalFileDeleteBatch {
$dotExt = $ext === '' ? '' : ".$ext";
foreach ( $this->srcRels as $name => $srcRel ) {
- // Skip files that have no hash (missing source).
- // Keep private files where they are.
- if ( isset( $hashes[$name] ) && !array_key_exists( $name, $privateFiles ) ) {
+ // Skip files that have no hash (e.g. missing DB record, or sha1 field and file source)
+ if ( isset( $hashes[$name] ) ) {
$hash = $hashes[$name];
$key = $hash . $dotExt;
$dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
@@ -2284,6 +2207,7 @@ class LocalFileDeleteBatch {
$this->doDBInserts();
// Removes non-existent file from the batch, so we don't get errors.
+ // This also handles files in the 'deleted' zone deleted via revision deletion.
$checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
if ( !$checkStatus->isGood() ) {
$this->status->merge( $checkStatus );
@@ -2303,7 +2227,6 @@ class LocalFileDeleteBatch {
// Roll back inserts, release lock and abort
// TODO: delete the defunct filearchive rows if we are using a non-transactional DB
$this->file->unlockAndRollback();
- wfProfileOut( __METHOD__ );
return $this->status;
}
@@ -2313,7 +2236,6 @@ class LocalFileDeleteBatch {
// Commit and return
$this->file->unlock();
- wfProfileOut( __METHOD__ );
return $this->status;
}
@@ -2366,7 +2288,7 @@ class LocalFileRestoreBatch {
/** @var bool Add all revisions of the file */
private $all;
- /** @var bool Wether to remove all settings for suppressed fields */
+ /** @var bool Whether to remove all settings for suppressed fields */
private $unsuppress = false;
/**
@@ -2419,13 +2341,19 @@ class LocalFileRestoreBatch {
return $this->file->repo->newGood();
}
- $this->file->lock();
+ $lockOwnsTrx = $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' ) );
+ array( 'img_name' => $this->file->getName() ),
+ __METHOD__,
+ // The lock() should already prevents changes, but this still may need
+ // to bypass any transaction snapshot. However, if lock() started the
+ // trx (which it probably did) then snapshot is post-lock and up-to-date.
+ $lockOwnsTrx ? array() : array( 'LOCK IN SHARE MODE' )
+ );
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
@@ -2797,7 +2725,7 @@ class LocalFileMoveBatch {
array( 'oi_archive_name', 'oi_deleted' ),
array( 'oi_name' => $this->oldName ),
__METHOD__,
- array( 'FOR UPDATE' ) // ignore snapshot
+ array( 'LOCK IN SHARE MODE' ) // ignore snapshot
);
foreach ( $result as $row ) {
diff --git a/includes/filerepo/file/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php
index 710058fb..fd92e11a 100644
--- a/includes/filerepo/file/OldLocalFile.php
+++ b/includes/filerepo/file/OldLocalFile.php
@@ -175,11 +175,12 @@ class OldLocalFile extends LocalFile {
}
function loadFromDB( $flags = 0 ) {
- wfProfileIn( __METHOD__ );
-
$this->dataLoaded = true;
- $dbr = $this->repo->getSlaveDB();
+ $dbr = ( $flags & self::READ_LATEST )
+ ? $this->repo->getMasterDB()
+ : $this->repo->getSlaveDB();
+
$conds = array( 'oi_name' => $this->getName() );
if ( is_null( $this->requestedTime ) ) {
$conds['oi_archive_name'] = $this->archive_name;
@@ -194,14 +195,12 @@ class OldLocalFile extends LocalFile {
$this->fileExists = false;
}
- wfProfileOut( __METHOD__ );
}
/**
* Load lazy file metadata from the DB
*/
protected function loadExtraFromDB() {
- wfProfileIn( __METHOD__ );
$this->extraDataLoaded = true;
$dbr = $this->repo->getSlaveDB();
@@ -226,11 +225,9 @@ class OldLocalFile extends LocalFile {
$this->$name = $value;
}
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( "Could not find data for image '{$this->archive_name}'." );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -260,13 +257,11 @@ class OldLocalFile extends LocalFile {
}
function upgradeRow() {
- wfProfileIn( __METHOD__ );
$this->loadFromFile();
# Don't destroy file info of missing files
if ( !$this->fileExists ) {
wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
- wfProfileOut( __METHOD__ );
return;
}
@@ -291,7 +286,6 @@ class OldLocalFile extends LocalFile {
'oi_archive_name' => $this->archive_name ),
__METHOD__
);
- wfProfileOut( __METHOD__ );
}
/**
diff --git a/includes/filerepo/file/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php
index 5a3e4e9c..4c11341b 100644
--- a/includes/filerepo/file/UnregisteredLocalFile.php
+++ b/includes/filerepo/file/UnregisteredLocalFile.php
@@ -166,6 +166,18 @@ class UnregisteredLocalFile extends File {
}
/**
+ * @return int
+ */
+ function getBitDepth() {
+ $gis = $this->getImageSize( $this->getLocalRefPath() );
+
+ if ( !$gis || !isset( $gis['bits'] ) ) {
+ return 0;
+ }
+ return $gis['bits'];
+ }
+
+ /**
* @return bool
*/
function getMetadata() {
diff --git a/includes/gallery/ImageGalleryBase.php b/includes/gallery/ImageGalleryBase.php
index b0a593de..c89c6b6c 100644
--- a/includes/gallery/ImageGalleryBase.php
+++ b/includes/gallery/ImageGalleryBase.php
@@ -30,39 +30,33 @@
abstract class ImageGalleryBase extends ContextSource {
/**
* @var array Gallery images
- * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
*/
- public $mImages;
+ protected $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;
+ protected $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;
+ protected $mShowFilename;
/**
* @var string Gallery mode. Default: traditional
- * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
*/
- public $mMode;
+ protected $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;
+ protected $mCaption = false;
/**
* @var bool Hide blacklisted images?
- * @deprecated since 1.23 (was declared "var") and will be removed in 1.24
*/
- public $mHideBadImages;
+ protected $mHideBadImages;
/**
* @var Parser Registered parser object for output callbacks
@@ -120,7 +114,7 @@ abstract class ImageGalleryBase extends ContextSource {
'packed-overlay' => 'PackedOverlayImageGallery',
);
// Allow extensions to make a new gallery format.
- wfRunHooks( 'GalleryGetModes', self::$modeMapping );
+ Hooks::run( 'GalleryGetModes', array( &self::$modeMapping ) );
}
}
diff --git a/includes/gallery/TraditionalImageGallery.php b/includes/gallery/TraditionalImageGallery.php
index 37f2221f..7a0206c2 100644
--- a/includes/gallery/TraditionalImageGallery.php
+++ b/includes/gallery/TraditionalImageGallery.php
@@ -72,7 +72,7 @@ class TraditionalImageGallery extends ImageGalleryBase {
if ( $this->mParser instanceof Parser ) {
# Give extensions a chance to select the file revision for us
$options = array();
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ Hooks::run( 'BeforeParserFetchFileAndTitle',
array( $this->mParser, $nt, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
list( $img, $nt ) = $this->mParser->fetchFileAndTitle( $nt, $options );
@@ -133,6 +133,8 @@ class TraditionalImageGallery extends ImageGalleryBase {
$this->adjustImageParameters( $thumb, $imageParameters );
+ Linker::processResponsiveImages( $img, $thumb, $transformOptions );
+
# Set both fixed width and min-height.
$thumbhtml = "\n\t\t\t"
. '<div class="thumb" style="width: '
diff --git a/includes/htmlform/HTMLCheckField.php b/includes/htmlform/HTMLCheckField.php
index 5f70362a..4942327f 100644
--- a/includes/htmlform/HTMLCheckField.php
+++ b/includes/htmlform/HTMLCheckField.php
@@ -20,28 +20,19 @@ class HTMLCheckField extends HTMLFormField {
$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
- );
- }
+ $chkLabel = Xml::check( $this->mName, $value, $attr )
+ . '&#160;'
+ . Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
- return $chkLabel;
+ if ( $wgUseMediaWikiUIEverywhere || $this->mParent instanceof VFormHTMLForm ) {
+ $chkLabel = Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-ui-checkbox' ),
+ $chkLabel
+ );
}
+
+ return $chkLabel;
}
/**
@@ -67,23 +58,16 @@ class HTMLCheckField extends HTMLFormField {
* @return string
*/
function loadDataFromRequest( $request ) {
- $invert = false;
- if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
- $invert = true;
- }
+ $invert = isset( $this->mParams['invert'] ) && $this->mParams['invert'];
// 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;
+ return $invert
+ ? !$request->getBool( $this->mName )
+ : $request->getBool( $this->mName );
} else {
return $this->getDefault();
}
diff --git a/includes/htmlform/HTMLCheckMatrix.php b/includes/htmlform/HTMLCheckMatrix.php
index 6c538fdd..83f12665 100644
--- a/includes/htmlform/HTMLCheckMatrix.php
+++ b/includes/htmlform/HTMLCheckMatrix.php
@@ -178,6 +178,13 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
$helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
$cellAttributes = array( 'colspan' => 2 );
+ $hideClass = '';
+ $hideAttributes = array();
+ if ( $this->mHideIf ) {
+ $hideAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
+ $hideClass = 'mw-htmlform-hide-if';
+ }
+
$label = $this->getLabelHtml( $cellAttributes );
$field = Html::rawElement(
@@ -186,9 +193,12 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
$inputHtml . "\n$errors"
);
- $html = Html::rawElement( 'tr', array( 'class' => 'mw-htmlform-vertical-label' ), $label );
+ $html = Html::rawElement( 'tr',
+ array( 'class' => "mw-htmlform-vertical-label $hideClass" ) + $hideAttributes,
+ $label );
$html .= Html::rawElement( 'tr',
- array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $hideClass" ) +
+ $hideAttributes,
$field );
return $html . $helptext;
diff --git a/includes/htmlform/HTMLFloatField.php b/includes/htmlform/HTMLFloatField.php
index 3b38fbe8..2ef49789 100644
--- a/includes/htmlform/HTMLFloatField.php
+++ b/includes/htmlform/HTMLFloatField.php
@@ -17,7 +17,7 @@ class HTMLFloatField extends HTMLTextField {
$value = trim( $value );
- # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
+ # http://www.w3.org/TR/html5/infrastructure.html#floating-point-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();
diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php
index d582da3b..ce140038 100644
--- a/includes/htmlform/HTMLForm.php
+++ b/includes/htmlform/HTMLForm.php
@@ -115,6 +115,8 @@ class HTMLForm extends ContextSource {
'info' => 'HTMLInfoField',
'selectorother' => 'HTMLSelectOrOtherField',
'selectandother' => 'HTMLSelectAndOtherField',
+ 'namespaceselect' => 'HTMLSelectNamespace',
+ 'tagfilter' => 'HTMLTagFilter',
'submit' => 'HTMLSubmitField',
'hidden' => 'HTMLHiddenField',
'edittools' => 'HTMLEditTools',
@@ -205,10 +207,42 @@ class HTMLForm extends ContextSource {
'table',
'div',
'raw',
+ 'inline',
+ );
+
+ /**
+ * Available formats in which to display the form
+ * @var array
+ */
+ protected $availableSubclassDisplayFormats = array(
'vform',
);
/**
+ * Construct a HTMLForm object for given display type. May return a HTMLForm subclass.
+ *
+ * @throws MWException When the display format requested is not known
+ * @param string $displayFormat
+ * @param mixed $arguments... Additional arguments to pass to the constructor.
+ * @return HTMLForm
+ */
+ public static function factory( $displayFormat/*, $arguments...*/ ) {
+ $arguments = func_get_args();
+ array_shift( $arguments );
+
+ switch ( $displayFormat ) {
+ case 'vform':
+ $reflector = new ReflectionClass( 'VFormHTMLForm' );
+ return $reflector->newInstanceArgs( $arguments );
+ default:
+ $reflector = new ReflectionClass( 'HTMLForm' );
+ $form = $reflector->newInstanceArgs( $arguments );
+ $form->setDisplayFormat( $displayFormat );
+ return $form;
+ }
+ }
+
+ /**
* Build a new HTMLForm from an array of field attributes
*
* @param array $descriptor Array of Field constructs, as described above
@@ -231,6 +265,11 @@ class HTMLForm extends ContextSource {
$this->mMessagePrefix = $context;
}
+ // Evil hack for mobile :(
+ if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $this->displayFormat === 'table' ) {
+ $this->displayFormat = 'div';
+ }
+
// Expand out into a tree.
$loadedDescriptor = array();
$this->mFlatFields = array();
@@ -244,15 +283,7 @@ class HTMLForm extends ContextSource {
$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 );
- }
+ $field = static::loadInputFromParameters( $fieldname, $info, $this );
$setSection =& $loadedDescriptor;
if ( $section ) {
@@ -287,10 +318,24 @@ class HTMLForm extends ContextSource {
* @return HTMLForm $this for chaining calls (since 1.20)
*/
public function setDisplayFormat( $format ) {
+ if (
+ in_array( $format, $this->availableSubclassDisplayFormats ) ||
+ in_array( $this->displayFormat, $this->availableSubclassDisplayFormats )
+ ) {
+ throw new MWException( 'Cannot change display format after creation, ' .
+ 'use HTMLForm::factory() instead' );
+ }
+
if ( !in_array( $format, $this->availableDisplayFormats ) ) {
throw new MWException( 'Display format must be one of ' .
print_r( $this->availableDisplayFormats, true ) );
}
+
+ // Evil hack for mobile :(
+ if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $format === 'table' ) {
+ $format = 'div';
+ }
+
$this->displayFormat = $format;
return $this;
@@ -302,20 +347,18 @@ class HTMLForm extends ContextSource {
* @return string
*/
public function getDisplayFormat() {
- $format = $this->displayFormat;
- if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $format === 'table' ) {
- $format = 'div';
- }
- return $format;
+ return $this->displayFormat;
}
/**
* Test if displayFormat is 'vform'
* @since 1.22
+ * @deprecated since 1.25
* @return bool
*/
public function isVForm() {
- return $this->displayFormat === 'vform';
+ wfDeprecated( __METHOD__, '1.25' );
+ return false;
}
/**
@@ -338,7 +381,7 @@ class HTMLForm extends ContextSource {
if ( isset( $descriptor['class'] ) ) {
$class = $descriptor['class'];
} elseif ( isset( $descriptor['type'] ) ) {
- $class = self::$typeMappings[$descriptor['type']];
+ $class = static::$typeMappings[$descriptor['type']];
$descriptor['class'] = $class;
} else {
$class = null;
@@ -357,14 +400,18 @@ class HTMLForm extends ContextSource {
*
* @param string $fieldname Name of the field
* @param array $descriptor Input Descriptor, as described above
+ * @param HTMLForm|null $parent Parent instance of HTMLForm
*
* @throws MWException
* @return HTMLFormField Instance of a subclass of HTMLFormField
*/
- public static function loadInputFromParameters( $fieldname, $descriptor ) {
- $class = self::getClassFromDescriptor( $fieldname, $descriptor );
+ public static function loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent = null ) {
+ $class = static::getClassFromDescriptor( $fieldname, $descriptor );
$descriptor['fieldname'] = $fieldname;
+ if ( $parent ) {
+ $descriptor['parent'] = $parent;
+ }
# @todo This will throw a fatal error whenever someone try to use
# 'class' to feed a CSS class instead of 'cssclass'. Would be
@@ -787,14 +834,6 @@ class HTMLForm extends ContextSource {
# 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 )
@@ -810,18 +849,10 @@ class HTMLForm extends ContextSource {
}
/**
- * Wrap the form innards in an actual "<form>" element
- *
- * @param string $html HTML contents to wrap.
- *
- * @return string Wrapped HTML.
+ * Get HTML attributes for the `<form>` tag.
+ * @return array
*/
- function wrapForm( $html ) {
-
- # Include a <fieldset> wrapper for style, if requested.
- if ( $this->mWrapperLegend !== false ) {
- $html = Xml::fieldset( $this->mWrapperLegend, $html );
- }
+ protected function getFormAttributes() {
# Use multipart/form-data
$encType = $this->mUseMultipart
? 'multipart/form-data'
@@ -836,12 +867,23 @@ class HTMLForm extends ContextSource {
if ( !empty( $this->mId ) ) {
$attribs['id'] = $this->mId;
}
+ return $attribs;
+ }
- if ( $this->isVForm() ) {
- array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' );
+ /**
+ * 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 );
}
- return Html::rawElement( 'form', $attribs, $html );
+ return Html::rawElement( 'form', $this->getFormAttributes(), $html );
}
/**
@@ -897,21 +939,10 @@ class HTMLForm extends ContextSource {
$attribs['class'] = array( 'mw-htmlform-submit' );
- if ( $this->isVForm() || $useMediaWikiUIEverywhere ) {
+ if ( $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";
}
@@ -920,7 +951,8 @@ class HTMLForm extends ContextSource {
'input',
array(
'type' => 'reset',
- 'value' => $this->msg( 'htmlform-reset' )->text()
+ 'value' => $this->msg( 'htmlform-reset' )->text(),
+ 'class' => ( $useMediaWikiUIEverywhere ? 'mw-ui-button' : null ),
)
) . "\n";
}
@@ -940,15 +972,9 @@ class HTMLForm extends ContextSource {
$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';
- }
+ if ( $useMediaWikiUIEverywhere ) {
+ $attrs['class'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : array();
+ $attrs['class'][] = 'mw-ui-button';
}
$buttons .= Html::element( 'input', $attrs ) . "\n";
@@ -957,13 +983,6 @@ class HTMLForm extends ContextSource {
$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;
}
@@ -1007,7 +1026,7 @@ class HTMLForm extends ContextSource {
*
* @return string HTML, a "<ul>" list of errors
*/
- public static function formatErrors( $errors ) {
+ public function formatErrors( $errors ) {
$errorstr = '';
foreach ( $errors as $error ) {
@@ -1021,7 +1040,7 @@ class HTMLForm extends ContextSource {
$errorstr .= Html::rawElement(
'li',
array(),
- wfMessage( $msg, $error )->parse()
+ $this->msg( $msg, $error )->parse()
);
}
@@ -1045,13 +1064,21 @@ class HTMLForm extends ContextSource {
/**
* Identify that the submit button in the form has a destructive action
- *
+ * @since 1.24
*/
public function setSubmitDestructive() {
$this->mSubmitModifierClass = 'mw-ui-destructive';
}
/**
+ * Identify that the submit button in the form has a progressive action
+ * @since 1.25
+ */
+ public function setSubmitProgressive() {
+ $this->mSubmitModifierClass = 'mw-ui-progressive';
+ }
+
+ /**
* Set the text for the submit button to a message
* @since 1.19
*
@@ -1268,20 +1295,8 @@ class HTMLForm extends ContextSource {
$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 );
- }
+ // Conveniently, PHP method names are case-insensitive.
+ $getFieldHtmlMethod = $displayFormat == 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
foreach ( $fields as $key => $value ) {
if ( $value instanceof HTMLFormField ) {
@@ -1353,7 +1368,9 @@ class HTMLForm extends ContextSource {
$html = Html::rawElement( 'table',
$attribs,
Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
- } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
+ } elseif ( $displayFormat === 'inline' ) {
+ $html = Html::rawElement( 'span', $attribs, "\n$html\n" );
+ } else {
$html = Html::rawElement( 'div', $attribs, "\n$html\n" );
}
}
diff --git a/includes/htmlform/HTMLFormField.php b/includes/htmlform/HTMLFormField.php
index 4cf23942..9576c77c 100644
--- a/includes/htmlform/HTMLFormField.php
+++ b/includes/htmlform/HTMLFormField.php
@@ -13,6 +13,7 @@ abstract class HTMLFormField {
protected $mLabel; # String label. Set on construction
protected $mID;
protected $mClass = '';
+ protected $mVFormClass = '';
protected $mHelpClass = false;
protected $mDefault;
protected $mOptions = false;
@@ -126,6 +127,7 @@ abstract class HTMLFormField {
* @param array $alldata
* @param array $params
* @return bool
+ * @throws MWException
*/
protected function isHiddenRecurse( array $alldata, array $params ) {
$origParams = $params;
@@ -217,7 +219,7 @@ abstract class HTMLFormField {
default:
throw new MWException( "Unknown operation" );
}
- } catch ( MWException $ex ) {
+ } catch ( Exception $ex ) {
throw new MWException(
"Invalid hide-if specification for $this->mName: " .
$ex->getMessage() . " in " . var_export( $origParams, true ),
@@ -343,6 +345,10 @@ abstract class HTMLFormField {
function __construct( $params ) {
$this->mParams = $params;
+ if ( isset( $params['parent'] ) && $params['parent'] instanceof HTMLForm ) {
+ $this->mParent = $params['parent'];
+ }
+
# Generate the label from a message, if possible
if ( isset( $params['label-message'] ) ) {
$msgInfo = $params['label-message'];
@@ -354,7 +360,7 @@ abstract class HTMLFormField {
$msgInfo = array();
}
- $this->mLabel = wfMessage( $msg, $msgInfo )->parse();
+ $this->mLabel = $this->msg( $msg, $msgInfo )->parse();
} elseif ( isset( $params['label'] ) ) {
if ( $params['label'] === '&#160;' ) {
// Apparently some things set &nbsp directly and in an odd format
@@ -507,10 +513,7 @@ abstract class HTMLFormField {
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';
- }
+ $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $this->mVFormClass, $errorClass );
$wrapperAttributes = array(
'class' => $divCssClasses,
@@ -550,6 +553,41 @@ abstract class HTMLFormField {
}
/**
+ * Get the complete field for the input, including help text,
+ * labels, and whatever. Fall back from 'vform' to 'div' when not overridden.
+ *
+ * @since 1.25
+ * @param string $value The value to set the input to.
+ * @return string Complete HTML field.
+ */
+ public function getVForm( $value ) {
+ // Ewwww
+ $this->mVFormClass = ' mw-ui-vform-field';
+ return $this->getDiv( $value );
+ }
+
+ /**
+ * Get the complete field as an inline element.
+ * @since 1.25
+ * @param string $value The value to set the input to.
+ * @return string Complete HTML inline element
+ */
+ public function getInline( $value ) {
+ list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
+ $inputHtml = $this->getInputHTML( $value );
+ $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
+ $cellAttributes = array();
+ $label = $this->getLabelHtml( $cellAttributes );
+
+ $html = "\n" . $errors .
+ $label . '&#160;' .
+ $inputHtml .
+ $helptext;
+
+ return $html;
+ }
+
+ /**
* Generate help text HTML in table format
* @since 1.20
*
diff --git a/includes/htmlform/HTMLFormFieldCloner.php b/includes/htmlform/HTMLFormFieldCloner.php
index 029911cd..b06f10d5 100644
--- a/includes/htmlform/HTMLFormFieldCloner.php
+++ b/includes/htmlform/HTMLFormFieldCloner.php
@@ -96,8 +96,7 @@ class HTMLFormFieldCloner extends HTMLFormField {
} else {
$info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--$fieldname" );
}
- $field = HTMLForm::loadInputFromParameters( $name, $info );
- $field->mParent = $this->mParent;
+ $field = HTMLForm::loadInputFromParameters( $name, $info, $this->mParent );
$fields[$fieldname] = $field;
}
return $fields;
@@ -263,19 +262,11 @@ class HTMLFormFieldCloner extends HTMLFormField {
? $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 );
- }
+ // Conveniently, PHP method names are case-insensitive.
+ $getFieldHtmlMethod = $displayFormat == 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
$html = '';
+ $hidden = '';
$hasLabel = false;
$fields = $this->createFieldsForKey( $key );
@@ -283,11 +274,18 @@ class HTMLFormFieldCloner extends HTMLFormField {
$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 ( $field instanceof HTMLHiddenField ) {
+ // HTMLHiddenField doesn't generate its own HTML
+ list( $name, $value, $params ) = $field->getHiddenFieldData( $v );
+ $hidden .= Html::hidden( $name, $value, $params ) . "\n";
+ } else {
+ $html .= $field->$getFieldHtmlMethod( $v );
+
+ $labelValue = trim( $field->getLabel() );
+ if ( $labelValue != '&#160;' && $labelValue !== '' ) {
+ $hasLabel = true;
+ }
}
}
@@ -302,8 +300,7 @@ class HTMLFormFieldCloner extends HTMLFormField {
'id' => Sanitizer::escapeId( "{$this->mID}--$key--delete" ),
'cssclass' => 'mw-htmlform-cloner-delete-button',
'default' => $this->msg( $label )->text(),
- ) );
- $field->mParent = $this->mParent;
+ ), $this->mParent );
$v = $field->getDefault();
if ( $displayFormat === 'table' ) {
@@ -330,11 +327,13 @@ class HTMLFormFieldCloner extends HTMLFormField {
$html = Html::rawElement( 'table',
$attribs,
Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
- } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
+ } else {
$html = Html::rawElement( 'div', $attribs, "\n$html\n" );
}
}
+ $html .= $hidden;
+
if ( !empty( $this->mParams['row-legend'] ) ) {
$legend = $this->msg( $this->mParams['row-legend'] )->text();
$html = Xml::fieldset( $legend, $html );
@@ -373,8 +372,7 @@ class HTMLFormFieldCloner extends HTMLFormField {
'id' => Sanitizer::escapeId( "{$this->mID}--create" ),
'cssclass' => 'mw-htmlform-cloner-create-button',
'default' => $this->msg( $label )->text(),
- ) );
- $field->mParent = $this->mParent;
+ ), $this->mParent );
$html .= $field->getInputHTML( $field->getDefault() );
return $html;
diff --git a/includes/htmlform/HTMLHiddenField.php b/includes/htmlform/HTMLHiddenField.php
index e32c0bb2..ffde9151 100644
--- a/includes/htmlform/HTMLHiddenField.php
+++ b/includes/htmlform/HTMLHiddenField.php
@@ -1,22 +1,36 @@
<?php
class HTMLHiddenField extends HTMLFormField {
+ protected $outputAsDefault = true;
+
public function __construct( $params ) {
parent::__construct( $params );
+ if ( isset( $this->mParams['output-as-default'] ) ) {
+ $this->outputAsDefault = (bool)$this->mParams['output-as-default'];
+ }
+
# 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 ) {
+ public function getHiddenFieldData( $value ) {
$params = array();
if ( $this->mID ) {
$params['id'] = $this->mID;
}
- $this->mParent->addHiddenField( $this->mName, $this->mDefault, $params );
+ if ( $this->outputAsDefault ) {
+ $value = $this->mDefault;
+ }
+
+ return array( $this->mName, $value, $params );
+ }
+ public function getTableRow( $value ) {
+ list( $name, $value, $params ) = $this->getHiddenFieldData( $value );
+ $this->mParent->addHiddenField( $name, $value, $params );
return '';
}
diff --git a/includes/htmlform/HTMLIntField.php b/includes/htmlform/HTMLIntField.php
index 28876e2c..b0148d98 100644
--- a/includes/htmlform/HTMLIntField.php
+++ b/includes/htmlform/HTMLIntField.php
@@ -11,14 +11,13 @@ class HTMLIntField extends HTMLFloatField {
return $p;
}
- # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
+ # http://www.w3.org/TR/html5/infrastructure.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 ) )
- ) {
+ if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) ) ) {
return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
}
diff --git a/includes/htmlform/HTMLMultiSelectField.php b/includes/htmlform/HTMLMultiSelectField.php
index 1b71ab95..8d28b59e 100644
--- a/includes/htmlform/HTMLMultiSelectField.php
+++ b/includes/htmlform/HTMLMultiSelectField.php
@@ -59,6 +59,14 @@ class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable
$label
);
+ if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $checkbox = Html::rawElement(
+ 'div',
+ array( 'class' => 'mw-ui-checkbox' ),
+ $checkbox
+ );
+ }
+
$html .= ' ' . Html::rawElement(
'div',
array( 'class' => 'mw-htmlform-flatlist-item' ),
diff --git a/includes/htmlform/HTMLRadioField.php b/includes/htmlform/HTMLRadioField.php
index 8765407b..0f005408 100644
--- a/includes/htmlform/HTMLRadioField.php
+++ b/includes/htmlform/HTMLRadioField.php
@@ -56,7 +56,7 @@ class HTMLRadioField extends HTMLFormField {
$html .= ' ' . Html::rawElement(
'div',
- array( 'class' => 'mw-htmlform-flatlist-item' ),
+ array( 'class' => 'mw-htmlform-flatlist-item mw-ui-radio' ),
$radio
);
}
diff --git a/includes/htmlform/HTMLSelectAndOtherField.php b/includes/htmlform/HTMLSelectAndOtherField.php
index 65176dd7..a1c0c957 100644
--- a/includes/htmlform/HTMLSelectAndOtherField.php
+++ b/includes/htmlform/HTMLSelectAndOtherField.php
@@ -13,6 +13,7 @@
class HTMLSelectAndOtherField extends HTMLSelectField {
function __construct( $params ) {
if ( array_key_exists( 'other', $params ) ) {
+ // Do nothing
} elseif ( array_key_exists( 'other-message', $params ) ) {
$params['other'] = wfMessage( $params['other-message'] )->plain();
} else {
@@ -22,7 +23,7 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
parent::__construct( $params );
if ( $this->getOptions() === null ) {
- # Sulk
+ // Sulk
throw new MWException( 'HTMLSelectAndOtherField called without any options' );
}
if ( !in_array( 'other', $this->mOptions, true ) ) {
@@ -39,10 +40,12 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
$textAttribs = array(
'id' => $this->mID . '-other',
'size' => $this->getSize(),
+ 'class' => array( 'mw-htmlform-select-and-other-field' ),
+ 'data-id-select' => $this->mID,
);
if ( $this->mClass !== '' ) {
- $textAttribs['class'] = $this->mClass;
+ $textAttribs['class'][] = $this->mClass;
}
$allowedParams = array(
@@ -50,7 +53,8 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
'autofocus',
'multiple',
'disabled',
- 'tabindex'
+ 'tabindex',
+ 'maxlength', // gets dynamic with javascript, see mediawiki.htmlform.js
);
$textAttribs += $this->getAttributes( $allowedParams );
@@ -71,6 +75,7 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
$list = $request->getText( $this->mName );
$text = $request->getText( $this->mName . '-other' );
+ // Should be built the same as in mediawiki.htmlform.js
if ( $list == 'other' ) {
$final = $text;
} elseif ( !in_array( $list, $this->mFlatOptions, true ) ) {
diff --git a/includes/htmlform/HTMLSelectNamespace.php b/includes/htmlform/HTMLSelectNamespace.php
new file mode 100644
index 00000000..96381062
--- /dev/null
+++ b/includes/htmlform/HTMLSelectNamespace.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * Wrapper for Html::namespaceSelector to use in HTMLForm
+ */
+class HTMLSelectNamespace extends HTMLFormField {
+ function getInputHTML( $value ) {
+ return Html::namespaceSelector(
+ array(
+ 'selected' => $value,
+ 'all' => 'all'
+ ), array(
+ 'name' => $this->mName,
+ 'id' => $this->mID,
+ 'class' => 'namespaceselector',
+ )
+ );
+ }
+}
diff --git a/includes/htmlform/HTMLTagFilter.php b/includes/htmlform/HTMLTagFilter.php
new file mode 100644
index 00000000..8075de5a
--- /dev/null
+++ b/includes/htmlform/HTMLTagFilter.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Wrapper for ChangeTags::buildTagFilterSelector to use in HTMLForm
+ */
+class HTMLTagFilter extends HTMLFormField {
+ protected $tagFilter;
+
+ function getTableRow( $value ) {
+ $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
+ if ( $this->tagFilter ) {
+ return parent::getTableRow( $value );
+ }
+ return '';
+ }
+
+ function getDiv( $value ) {
+ $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
+ if ( $this->tagFilter ) {
+ return parent::getDiv( $value );
+ }
+ return '';
+ }
+
+ function getInputHTML( $value ) {
+ if ( $this->tagFilter ) {
+ // we only need the select field, HTMLForm should handle the label
+ return $this->tagFilter[1];
+ }
+ return '';
+ }
+}
diff --git a/includes/htmlform/HTMLTextField.php b/includes/htmlform/HTMLTextField.php
index 10bc67f0..88df49db 100644
--- a/includes/htmlform/HTMLTextField.php
+++ b/includes/htmlform/HTMLTextField.php
@@ -20,6 +20,7 @@ class HTMLTextField extends HTMLFormField {
# @todo Enforce pattern, step, required, readonly on the server side as
# well
$allowedParams = array(
+ 'type',
'min',
'max',
'pattern',
@@ -38,10 +39,13 @@ class HTMLTextField extends HTMLFormField {
$attribs += $this->getAttributes( $allowedParams );
+ # Extract 'type'
+ $type = isset( $attribs['type'] ) ? $attribs['type'] : 'text';
+ unset( $attribs['type'] );
+
# 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':
@@ -60,6 +64,7 @@ class HTMLTextField extends HTMLFormField {
break;
}
}
+
return Html::input( $this->mName, $value, $type, $attribs );
}
}
diff --git a/includes/htmlform/VFormHTMLForm.php b/includes/htmlform/VFormHTMLForm.php
new file mode 100644
index 00000000..0c0e4252
--- /dev/null
+++ b/includes/htmlform/VFormHTMLForm.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * HTML form generation and submission handling, vertical-form style.
+ *
+ * This 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
+ */
+
+/**
+ * Compact stacked vertical format for forms.
+ */
+class VFormHTMLForm extends HTMLForm {
+ /**
+ * Wrapper and its legend are never generated in VForm mode.
+ * @var boolean
+ */
+ protected $mWrapperLegend = false;
+
+ /**
+ * Symbolic display format name.
+ * @var string
+ */
+ protected $displayFormat = 'vform';
+
+ public function isVForm() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return true;
+ }
+
+ public static function loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent = null ) {
+ $field = parent::loadInputFromParameters( $fieldname, $descriptor, $parent );
+ $field->setShowEmptyLabel( false );
+ return $field;
+ }
+
+ function getHTML( $submitResult ) {
+ // This is required for VForm HTMLForms that use that style regardless
+ // of wgUseMediaWikiUIEverywhere (since they pre-date it).
+ // When wgUseMediaWikiUIEverywhere is removed, this should be consolidated
+ // with the addModuleStyles in SpecialPage->setHeaders.
+ $this->getOutput()->addModuleStyles( array(
+ 'mediawiki.ui',
+ 'mediawiki.ui.button',
+ 'mediawiki.ui.input',
+ 'mediawiki.ui.checkbox',
+ ) );
+
+ return parent::getHTML( $submitResult );
+ }
+
+ protected function getFormAttributes() {
+ $attribs = parent::getFormAttributes();
+ array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' );
+ return $attribs;
+ }
+
+ function wrapForm( $html ) {
+ // Always discard $this->mWrapperLegend
+ return Html::rawElement( 'form', $this->getFormAttributes(), $html );
+ }
+
+ function getButtons() {
+ $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',
+ 'mw-ui-button mw-ui-big mw-ui-block',
+ $this->mSubmitModifierClass,
+ );
+
+ $buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
+ }
+
+ if ( $this->mShowReset ) {
+ $buttons .= Html::element(
+ 'input',
+ array(
+ 'type' => 'reset',
+ 'value' => $this->msg( 'htmlform-reset' )->text(),
+ 'class' => 'mw-ui-button mw-ui-big mw-ui-block',
+ )
+ ) . "\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'];
+ }
+
+ $attrs['class'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : array();
+ $attrs['class'][] = 'mw-ui-button mw-ui-big mw-ui-block';
+
+ $buttons .= Html::element( 'input', $attrs ) . "\n";
+ }
+
+ $html = Html::rawElement( 'div',
+ array( 'class' => 'mw-htmlform-submit-buttons' ), "\n$buttons" ) . "\n";
+
+ return $html;
+ }
+}
diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php
index 31b93c88..6ccf2d55 100644
--- a/includes/installer/DatabaseInstaller.php
+++ b/includes/installer/DatabaseInstaller.php
@@ -305,7 +305,7 @@ abstract class DatabaseInstaller {
$up = DatabaseUpdater::newForDB( $this->db );
try {
$up->doUpdates();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
echo "\nAn error occurred:\n";
echo $e->getText();
$ret = false;
@@ -369,12 +369,17 @@ abstract class DatabaseInstaller {
}
/**
- * Get a name=>value map of MW configuration globals that overrides.
- * DefaultSettings.php
+ * Get a name=>value map of MW configuration globals for the default values.
* @return array
*/
public function getGlobalDefaults() {
- return array();
+ $defaults = array();
+ foreach ( $this->getGlobalNames() as $var ) {
+ if ( isset( $GLOBALS[$var] ) ) {
+ $defaults[$var] = $GLOBALS[$var];
+ }
+ }
+ return $defaults;
}
/**
@@ -657,7 +662,7 @@ abstract class DatabaseInstaller {
if ( $row == "" ) {
continue;
}
- $row .= "||";
+ $row .= "|";
$interwikis[] = array_combine(
array( 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ),
explode( '|', $row )
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
index 193d5920..702f850c 100644
--- a/includes/installer/DatabaseUpdater.php
+++ b/includes/installer/DatabaseUpdater.php
@@ -31,6 +31,7 @@ require_once __DIR__ . '/../../maintenance/Maintenance.php';
* @since 1.17
*/
abstract class DatabaseUpdater {
+ protected static $updateCounter = 0;
/**
* Array of updates to perform on the database
@@ -114,7 +115,7 @@ abstract class DatabaseUpdater {
$this->maintenance->setDB( $db );
$this->initOldGlobals();
$this->loadExtensions();
- wfRunHooks( 'LoadExtensionSchemaUpdates', array( $this ) );
+ Hooks::run( 'LoadExtensionSchemaUpdates', array( $this ) );
}
/**
@@ -460,7 +461,8 @@ abstract class DatabaseUpdater {
if ( !$this->canUseNewUpdatelog() ) {
return;
}
- $key = "updatelist-$version-" . time();
+ $key = "updatelist-$version-" . time() . self::$updateCounter;
+ self::$updateCounter++;
$this->db->insert( 'updatelog',
array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ),
__METHOD__ );
@@ -610,7 +612,7 @@ abstract class DatabaseUpdater {
* Append a line to the open filehandle. The line is assumed to
* be a complete SQL statement.
*
- * This is used as a callback for for sourceLine().
+ * This is used as a callback for sourceLine().
*
* @param string $line Text to append to the file
* @return bool False to skip actually executing the file
@@ -896,6 +898,29 @@ abstract class DatabaseUpdater {
}
/**
+ * Set any .htaccess files or equivilent for storage repos
+ *
+ * Some zones (e.g. "temp") used to be public and may have been initialized as such
+ */
+ public function setFileAccess() {
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $zonePath = $repo->getZonePath( 'temp' );
+ if ( $repo->getBackend()->directoryExists( array( 'dir' => $zonePath ) ) ) {
+ // If the directory was never made, then it will have the right ACLs when it is made
+ $status = $repo->getBackend()->secure( array(
+ 'dir' => $zonePath,
+ 'noAccess' => true,
+ 'noListing' => true
+ ) );
+ if ( $status->isOK() ) {
+ $this->output( "Set the local repo temp zone container to be private.\n" );
+ } else {
+ $this->output( "Failed to set the local repo temp zone container to be private.\n" );
+ }
+ }
+ }
+
+ /**
* Purge the objectcache table
*/
public function purgeCache() {
@@ -907,7 +932,9 @@ abstract class DatabaseUpdater {
if ( $wgLocalisationCacheConf['manualRecache'] ) {
$this->rebuildLocalisationCache();
}
- MessageBlobStore::getInstance()->clear();
+ $blobStore = new MessageBlobStore();
+ $blobStore->clear();
+ $this->db->delete( 'module_deps', '*', __METHOD__ );
$this->output( "done.\n" );
}
@@ -1034,6 +1061,31 @@ abstract class DatabaseUpdater {
}
/**
+ * Enable profiling table when it's turned on
+ */
+ protected function doEnableProfiling() {
+ global $wgProfiler;
+
+ if ( !$this->doTable( 'profiling' ) ) {
+ return true;
+ }
+
+ $profileToDb = false;
+ if ( isset( $wgProfiler['output'] ) ) {
+ $out = $wgProfiler['output'];
+ if ( $out === 'db' ) {
+ $profileToDb = true;
+ } elseif ( is_array( $out ) && in_array( 'db', $out ) ) {
+ $profileToDb = true;
+ }
+ }
+
+ if ( $profileToDb && !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
+ $this->applyPatch( 'patch-profiling.sql', false, 'Add profiling table' );
+ }
+ }
+
+ /**
* Rebuilds the localisation cache
*/
protected function rebuildLocalisationCache() {
diff --git a/includes/installer/InstallDocFormatter.php b/includes/installer/InstallDocFormatter.php
index 3250ff8a..0d52e64d 100644
--- a/includes/installer/InstallDocFormatter.php
+++ b/includes/installer/InstallDocFormatter.php
@@ -44,25 +44,31 @@ class InstallDocFormatter {
// Replace tab indents with colons
$text = preg_replace( '/^\t\t/m', '::', $text );
$text = preg_replace( '/^\t/m', ':', $text );
+
+ $linkStart = '<span class="config-plainlink">[';
+ $linkEnd = ' $0]</span>';
+
+ // turn (Tnnnn) into links
+ $text = preg_replace(
+ '/T\d+/',
+ "{$linkStart}https://phabricator.wikimedia.org/$0{$linkEnd}",
+ $text
+ );
+
// turn (bug nnnn) into links
- $text = preg_replace_callback( '/bug (\d+)/', array( $this, 'replaceBugLinks' ), $text );
+ $text = preg_replace(
+ '/bug (\d+)/',
+ "{$linkStart}https://bugzilla.wikimedia.org/$1{$linkEnd}",
+ $text
+ );
+
// add links to manual to every global variable mentioned
- $text = preg_replace_callback(
- '/(\$wg[a-z0-9_]+)/i',
- array( $this, 'replaceConfigLinks' ),
+ $text = preg_replace(
+ '/\$wg[a-z0-9_]+/i',
+ "{$linkStart}https://www.mediawiki.org/wiki/Manual:$0{$linkEnd}",
$text
);
return $text;
}
-
- protected function replaceBugLinks( $matches ) {
- return '<span class="config-plainlink">[https://bugzilla.wikimedia.org/' .
- $matches[1] . ' bug ' . $matches[1] . ']</span>';
- }
-
- protected function replaceConfigLinks( $matches ) {
- return '<span class="config-plainlink">[https://www.mediawiki.org/wiki/Manual:' .
- $matches[1] . ' ' . $matches[1] . ']</span>';
- }
}
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index d2651ae5..5ae499db 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -186,7 +186,6 @@ abstract class Installer {
'wgUseInstantCommons',
'wgUpgradeKey',
'wgDefaultSkin',
- 'wgResourceLoaderMaxQueryLength',
);
/**
@@ -225,7 +224,7 @@ abstract class Installer {
// $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',
+ 'wgLogo' => '$wgResourceBasePath/resources/assets/wiki.png',
);
/**
@@ -285,28 +284,28 @@ abstract class Installer {
*/
public $licenses = array(
'cc-by' => array(
- 'url' => 'http://creativecommons.org/licenses/by/3.0/',
- 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by.png',
+ 'url' => 'https://creativecommons.org/licenses/by/3.0/',
+ 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by.png',
),
'cc-by-sa' => array(
- 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
- 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-sa.png',
+ 'url' => 'https://creativecommons.org/licenses/by-sa/3.0/',
+ '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' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-nc-sa.png',
+ 'url' => 'https://creativecommons.org/licenses/by-nc-sa/3.0/',
+ 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png',
),
'cc-0' => array(
'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
- 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-0.png',
+ 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
),
'pd' => array(
'url' => '',
- 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/public-domain.png',
+ 'icon' => '$wgResourceBasePath/resources/assets/licenses/public-domain.png',
),
'gfdl' => array(
- 'url' => 'http://www.gnu.org/copyleft/fdl.html',
- 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/gnu-fdl.png',
+ 'url' => 'https://www.gnu.org/copyleft/fdl.html',
+ 'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
),
'none' => array(
'url' => '',
@@ -674,7 +673,6 @@ abstract class Installer {
'site_stats',
array(
'ss_row_id' => 1,
- 'ss_total_views' => 0,
'ss_total_edits' => 0,
'ss_good_articles' => 0,
'ss_total_pages' => 0,
@@ -728,7 +726,7 @@ abstract class Installer {
}
$databases = array_flip( $databases );
if ( !$databases ) {
- $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
+ $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) );
// @todo FIXME: This only works for the web installer!
return false;
@@ -898,11 +896,12 @@ abstract class Installer {
}
/**
- * Scare user to death if they have mod_security
+ * Scare user to death if they have mod_security or mod_security2
* @return bool
*/
protected function envCheckModSecurity() {
- if ( self::apacheModulePresent( 'mod_security' ) ) {
+ if ( self::apacheModulePresent( 'mod_security' )
+ || self::apacheModulePresent( 'mod_security2' ) ) {
$this->showMessage( 'config-mod-security' );
}
@@ -1141,9 +1140,6 @@ abstract class Installer {
* Check the libicu version
*/
protected function envCheckLibicu() {
- $utf8 = function_exists( 'utf8_normalize' );
- $intl = function_exists( 'normalizer_normalize' );
-
/**
* This needs to be updated something that the latest libicu
* will properly normalize. This normalization was found at
@@ -1157,18 +1153,7 @@ abstract class Installer {
$useNormalizer = 'php';
$needsUpdate = false;
- /**
- * We're going to prefer the pecl extension here unless
- * utf8_normalize is more up to date.
- */
- if ( $utf8 ) {
- $useNormalizer = 'utf8';
- $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
- if ( $utf8 !== $normal_c ) {
- $needsUpdate = true;
- }
- }
- if ( $intl ) {
+ if ( function_exists( 'normalizer_normalize' ) ) {
$useNormalizer = 'intl';
$intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
if ( $intl !== $normal_c ) {
@@ -1176,8 +1161,7 @@ abstract class Installer {
}
}
- // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8',
- // 'config-unicode-using-intl'
+ // Uses messages 'config-unicode-using-php' and 'config-unicode-using-intl'
if ( $useNormalizer === 'php' ) {
$this->showMessage( 'config-unicode-pure-php-warning' );
} else {
@@ -1376,8 +1360,8 @@ abstract class Installer {
}
try {
- $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
- } catch ( MWException $e ) {
+ $text = Http::get( $url . $file, array( 'timeout' => 3 ), __METHOD__ );
+ } catch ( Exception $e ) {
// Http::get throws with allow_url_fopen = false and no curl extension.
$text = null;
}
@@ -1452,13 +1436,16 @@ abstract class Installer {
return array();
}
+ // extensions -> extension.json, skins -> skin.json
+ $jsonFile = substr( $directory, 0, strlen( $directory ) -1 ) . '.json';
+
$dh = opendir( $extDir );
$exts = array();
while ( ( $file = readdir( $dh ) ) !== false ) {
if ( !is_dir( "$extDir/$file" ) ) {
continue;
}
- if ( file_exists( "$extDir/$file/$file.php" ) ) {
+ if ( file_exists( "$extDir/$file/$jsonFile" ) || file_exists( "$extDir/$file/$file.php" ) ) {
$exts[] = $file;
}
}
@@ -1469,15 +1456,16 @@ abstract class Installer {
}
/**
- * Returns a default value to be used for $wgDefaultSkin: the preferred skin, if available among
- * the installed skins, or any other one otherwise.
+ * Returns a default value to be used for $wgDefaultSkin: normally the one set in DefaultSettings,
+ * but will fall back to another if the default skin is missing and some other one is present
+ * instead.
*
* @param string[] $skinNames Names of installed skins.
* @return string
*/
public function getDefaultSkin( array $skinNames ) {
$defaultSkin = $GLOBALS['wgDefaultSkin'];
- if ( in_array( $defaultSkin, $skinNames ) ) {
+ if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) {
return $defaultSkin;
} else {
return $skinNames[0];
@@ -1504,16 +1492,31 @@ abstract class Installer {
*/
global $wgAutoloadClasses;
$wgAutoloadClasses = array();
+ $queue = array();
require "$IP/includes/DefaultSettings.php";
foreach ( $exts as $e ) {
- require_once "$IP/extensions/$e/$e.php";
+ if ( file_exists( "$IP/extensions/$e/extension.json" ) ) {
+ $queue["$IP/extensions/$e/extension.json"] = 1;
+ } else {
+ require_once "$IP/extensions/$e/$e.php";
+ }
}
+ $registry = new ExtensionRegistry();
+ $data = $registry->readFromQueue( $queue );
+ $wgAutoloadClasses += $data['autoload'];
+
$hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
$wgHooks['LoadExtensionSchemaUpdates'] : array();
+ if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
+ $hooksWeWant = array_merge_recursive(
+ $hooksWeWant,
+ $data['globals']['wgHooks']['LoadExtensionSchemaUpdates']
+ );
+ }
// Unset everyone else's hooks. Lord knows what someone might be doing
// in ParserFirstCallInit (see bug 27171)
$GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
@@ -1723,7 +1726,7 @@ abstract class Installer {
if ( MWHttpRequest::canMakeRequests() ) {
$res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
- array( 'method' => 'POST', 'postData' => $params ) )->execute();
+ array( 'method' => 'POST', 'postData' => $params ), __METHOD__ )->execute();
if ( !$res->isOK() ) {
$s->warning( 'config-install-subscribe-fail', $res->getMessage() );
}
@@ -1753,7 +1756,7 @@ abstract class Installer {
false,
User::newFromName( 'MediaWiki default' )
);
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
//using raw, because $wgShowExceptionDetails can not be set yet
$status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
}
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
index c0ba300d..162a7897 100644
--- a/includes/installer/LocalSettingsGenerator.php
+++ b/includes/installer/LocalSettingsGenerator.php
@@ -34,6 +34,7 @@ class LocalSettingsGenerator {
protected $groupPermissions = array();
protected $dbSettings = '';
protected $safeMode = false;
+ protected $IP;
/**
* @var Installer
@@ -50,6 +51,7 @@ class LocalSettingsGenerator {
$this->extensions = $installer->getVar( '_Extensions' );
$this->skins = $installer->getVar( '_Skins' );
+ $this->IP = $installer->getVar( 'IP' );
$db = $installer->getDBInstaller( $installer->getVar( 'wgDBtype' ) );
@@ -63,7 +65,7 @@ class LocalSettingsGenerator {
'wgRightsText', 'wgMainCacheType', 'wgEnableUploads',
'wgMainCacheType', '_MemCachedServers', 'wgDBserver', 'wgDBuser',
'wgDBpassword', 'wgUseInstantCommons', 'wgUpgradeKey', 'wgDefaultSkin',
- 'wgMetaNamespace', 'wgResourceLoaderMaxQueryLength', 'wgLogo',
+ 'wgMetaNamespace', 'wgLogo',
),
$db->getGlobalNames()
);
@@ -143,8 +145,7 @@ class LocalSettingsGenerator {
# 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 .= $this->generateExtEnableLine( 'skins', $skinName );
}
$localSettings .= "\n";
@@ -157,8 +158,7 @@ class LocalSettingsGenerator {
# The following extensions were automatically enabled:\n";
foreach ( $this->extensions as $extName ) {
- $encExtName = self::escapePhpString( $extName );
- $localSettings .= "require_once \"\$IP/extensions/$encExtName/$encExtName.php\";\n";
+ $localSettings .= $this->generateExtEnableLine( 'extensions', $extName );
}
$localSettings .= "\n";
@@ -172,6 +172,34 @@ class LocalSettingsGenerator {
}
/**
+ * Generate the appropriate line to enable the given extension or skin
+ *
+ * @param string $dir Either "extensions" or "skins"
+ * @param string $name Name of extension/skin
+ * @throws InvalidArgumentException
+ * @return string
+ */
+ private function generateExtEnableLine( $dir, $name ) {
+ if ( $dir === 'extensions' ) {
+ $jsonFile = 'extension.json';
+ $function = 'wfLoadExtension';
+ } elseif ( $dir === 'skins' ) {
+ $jsonFile = 'skin.json';
+ $function = 'wfLoadSkin';
+ } else {
+ throw new InvalidArgumentException( '$dir was not "extensions" or "skins' );
+ }
+
+ $encName = self::escapePhpString( $name );
+
+ if ( file_exists( "{$this->IP}/$dir/$encName/$jsonFile" ) ) {
+ return "$function( '$encName' );\n";
+ } else {
+ return "require_once \"\$IP/$dir/$encName/$encName.php\";\n";
+ }
+ }
+
+ /**
* Write the generated LocalSettings to a file
*
* @param string $fileName Full path to filename to write to
@@ -307,6 +335,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
${serverSetting}
## The relative URL path to the skins directory
\$wgStylePath = \"\$wgScriptPath/skins\";
+\$wgResourceBasePath = \$wgScriptPath;
## 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!
diff --git a/includes/installer/MssqlInstaller.php b/includes/installer/MssqlInstaller.php
index 46bb86c0..5a8403f5 100644
--- a/includes/installer/MssqlInstaller.php
+++ b/includes/installer/MssqlInstaller.php
@@ -652,9 +652,9 @@ class MssqlInstaller extends DatabaseInstaller {
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(
+ return array_merge( parent::getGlobalDefaults(), array(
'wgDBmwschema' => 'mediawiki',
- );
+ ) );
}
/**
diff --git a/includes/installer/MssqlUpdater.php b/includes/installer/MssqlUpdater.php
index ed11f8b6..5eef3355 100644
--- a/includes/installer/MssqlUpdater.php
+++ b/includes/installer/MssqlUpdater.php
@@ -42,6 +42,11 @@ class MssqlUpdater extends DatabaseUpdater {
// 1.24
array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql'),
+
+ // 1.25
+ array( 'dropTable', 'hitcounter' ),
+ array( 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ),
+ array( 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ),
// Constraint updates
array( 'updateConstraints', 'category_types', 'categorylinks', 'cl_type' ),
array( 'updateConstraints', 'major_mime', 'filearchive', 'fa_major_mime' ),
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
index b82e6114..3af08d60 100644
--- a/includes/installer/MysqlInstaller.php
+++ b/includes/installer/MysqlInstaller.php
@@ -47,7 +47,7 @@ class MysqlInstaller extends DatabaseInstaller {
public $supportedEngines = array( 'InnoDB', 'MyISAM' );
- public $minimumVersion = '5.0.2';
+ public $minimumVersion = '5.0.3';
public $webUserPrivs = array(
'DELETE',
@@ -72,13 +72,6 @@ class MysqlInstaller extends DatabaseInstaller {
}
/**
- * @return array
- */
- public function getGlobalDefaults() {
- return array();
- }
-
- /**
* @return string
*/
public function getConnectForm() {
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
index 990b5b03..52a09222 100644
--- a/includes/installer/MysqlUpdater.php
+++ b/includes/installer/MysqlUpdater.php
@@ -37,7 +37,6 @@ class MysqlUpdater extends DatabaseUpdater {
array( 'addField', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ),
array( 'doInterwikiUpdate' ),
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' ),
@@ -266,6 +265,11 @@ class MysqlUpdater extends DatabaseUpdater {
'patch-oi_major_mime-chemical.sql' ),
array( 'modifyField', 'filearchive', 'fa_major_mime',
'patch-fa_major_mime-chemical.sql' ),
+
+ // 1.25
+ array( 'doUserNewTalkUseridUnsigned' ),
+ // note this patch covers other _comment and _description fields too
+ array( 'modifyField', 'recentchanges', 'rc_comment', 'patch-editsummary-length.sql' ),
);
}
@@ -516,7 +520,6 @@ class MysqlUpdater extends DatabaseUpdater {
page_namespace int NOT NULL,
page_title varchar(255) binary NOT NULL,
page_restrictions tinyblob NOT NULL,
- page_counter bigint(20) unsigned NOT NULL default '0',
page_is_redirect tinyint(1) unsigned NOT NULL default '0',
page_is_new tinyint(1) unsigned NOT NULL default '0',
page_random real unsigned NOT NULL,
@@ -598,9 +601,9 @@ class MysqlUpdater extends DatabaseUpdater {
$this->output( "......Setting up page table.\n" );
$this->db->query(
"INSERT INTO $page (page_id, page_namespace, page_title,
- page_restrictions, page_counter, page_is_redirect, page_is_new, page_random,
+ page_restrictions, page_is_redirect, page_is_new, page_random,
page_touched, page_latest, page_len)
- SELECT cur_id, cur_namespace, cur_title, cur_restrictions, cur_counter,
+ SELECT cur_id, cur_namespace, cur_title, cur_restrictions,
cur_is_redirect, cur_is_new, cur_random, cur_touched, rev_id, LENGTH(cur_text)
FROM $cur,$revision
WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}",
@@ -924,18 +927,6 @@ class MysqlUpdater extends DatabaseUpdater {
}
}
- protected function doEnableProfiling() {
- global $wgProfileToDatabase;
-
- if ( !$this->doTable( 'profiling' ) ) {
- return true;
- }
-
- if ( $wgProfileToDatabase === true && !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
- $this->applyPatch( 'patch-profiling.sql', false, 'Add profiling table' );
- }
- }
-
protected function doMaybeProfilingMemoryUpdate() {
if ( !$this->doTable( 'profiling' ) ) {
return true;
@@ -1071,4 +1062,26 @@ class MysqlUpdater extends DatabaseUpdater {
'Making iwl_prefix_title_from index non-UNIQUE'
);
}
+
+ protected function doUserNewTalkUseridUnsigned() {
+ if ( !$this->doTable( 'user_newtalk' ) ) {
+ return true;
+ }
+
+ $info = $this->db->fieldInfo( 'user_newtalk', 'user_id' );
+ if ( $info === false ) {
+ return true;
+ }
+ if ( $info->isUnsigned() ) {
+ $this->output( "...user_id is already unsigned int.\n" );
+
+ return true;
+ }
+
+ return $this->applyPatch(
+ 'patch-user-newtalk-userid-unsigned.sql',
+ false,
+ 'Making user_id unsigned int'
+ );
+ }
}
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
index 18468544..03dbd1ce 100644
--- a/includes/installer/OracleUpdater.php
+++ b/includes/installer/OracleUpdater.php
@@ -103,6 +103,11 @@ class OracleUpdater extends DatabaseUpdater {
// 1.24
array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ),
+ // 1.25
+ array( 'dropTable', 'hitcounter' ),
+ array( 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ),
+ array( 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ),
+
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
@@ -172,7 +177,6 @@ class OracleUpdater extends DatabaseUpdater {
'page_id' => 0,
'page_namespace' => 0,
'page_title' => ' ',
- 'page_counter' => 0,
'page_is_redirect' => 0,
'page_is_new' => 0,
'page_random' => 0,
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
index c30a989e..b18fe944 100644
--- a/includes/installer/PostgresInstaller.php
+++ b/includes/installer/PostgresInstaller.php
@@ -262,11 +262,13 @@ class PostgresInstaller extends DatabaseInstaller {
$status = Status::newGood();
foreach ( $dbs as $db ) {
try {
- $conn = new DatabasePostgres(
- $this->getVar( 'wgDBserver' ),
- $user,
- $password,
- $db );
+ $p = array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $user,
+ 'password' => $password,
+ 'dbname' => $db
+ );
+ $conn = DatabaseBase::factory( 'postgres', $p );
} catch ( DBConnectionError $error ) {
$conn = false;
$status->fatal( 'config-pg-test-error', $db,
@@ -627,9 +629,9 @@ class PostgresInstaller extends DatabaseInstaller {
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(
+ return array_merge( parent::getGlobalDefaults(), array(
'wgDBmwschema' => 'mediawiki',
- );
+ ) );
}
public function setupPLpgSQL() {
diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php
index df2f0e32..6ac54360 100644
--- a/includes/installer/PostgresUpdater.php
+++ b/includes/installer/PostgresUpdater.php
@@ -417,9 +417,11 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgField', 'templatelinks', 'tl_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ),
array( 'addPgField', 'imagelinks', 'il_from_namespace', 'INTEGER NOT NULL DEFAULT 0' ),
- // 1.24.1 (backport from 1.25)
+ // 1.25
+ array( 'dropTable', 'hitcounter' ),
+ array( 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ),
+ array( 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ),
array( 'dropFkey', 'recentchanges', 'rc_cur_id' )
-
);
}
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
index 351b0223..f990ddf5 100644
--- a/includes/installer/SqliteInstaller.php
+++ b/includes/installer/SqliteInstaller.php
@@ -55,7 +55,7 @@ class SqliteInstaller extends DatabaseInstaller {
public function checkPrerequisites() {
$result = Status::newGood();
// Bail out if SQLite is too old
- $db = new DatabaseSqliteStandalone( ':memory:' );
+ $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
if ( version_compare( $db->getServerVersion(), self::MINIMUM_VERSION, '<' ) ) {
$result->fatal( 'config-outdated-sqlite', $db->getServerVersion(), self::MINIMUM_VERSION );
}
@@ -68,6 +68,7 @@ class SqliteInstaller extends DatabaseInstaller {
}
public function getGlobalDefaults() {
+ $defaults = parent::getGlobalDefaults();
if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
$path = str_replace(
array( '/', '\\' ),
@@ -75,10 +76,9 @@ class SqliteInstaller extends DatabaseInstaller {
dirname( $_SERVER['DOCUMENT_ROOT'] ) . '/data'
);
- return array( 'wgSQLiteDataDir' => $path );
- } else {
- return array();
+ $defaults['wgSQLiteDataDir'] = $path;
}
+ return $defaults;
}
public function getConnectForm() {
@@ -188,7 +188,7 @@ class SqliteInstaller extends DatabaseInstaller {
# @todo FIXME: Need more sensible constructor parameters, e.g. single associative array
# Setting globals kind of sucks
$wgSQLiteDataDir = $dir;
- $db = new DatabaseSqlite( false, false, false, $dbName );
+ $db = DatabaseBase::factory( 'sqlite', array( 'dbname' => $dbName ) );
$status->value = $db;
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
@@ -226,6 +226,49 @@ class SqliteInstaller extends DatabaseInstaller {
}
$db = $this->getVar( 'wgDBname' );
+
+ # Make the main and cache stub DB files
+ $status = Status::newGood();
+ $status->merge( $this->makeStubDBFile( $dir, $db ) );
+ $status->merge( $this->makeStubDBFile( $dir, "wikicache" ) );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ # Nuke the unused settings for clarity
+ $this->setVar( 'wgDBserver', '' );
+ $this->setVar( 'wgDBuser', '' );
+ $this->setVar( 'wgDBpassword', '' );
+ $this->setupSchemaVars();
+
+ # Create the global cache DB
+ try {
+ global $wgSQLiteDataDir;
+ # @todo FIXME: setting globals kind of sucks
+ $wgSQLiteDataDir = $dir;
+ $conn = DatabaseBase::factory( 'sqlite', array( 'dbname' => "wikicache" ) );
+ # @todo: don't duplicate objectcache definition, though it's very simple
+ $sql =
+<<<EOT
+ CREATE TABLE IF NOT EXISTS objectcache (
+ keyname BLOB NOT NULL default '' PRIMARY KEY,
+ value BLOB,
+ exptime TEXT
+ )
+EOT;
+ $conn->query( $sql );
+ $conn->query( "CREATE INDEX IF NOT EXISTS exptime ON objectcache (exptime)" );
+ $conn->query( "PRAGMA journal_mode=WAL" ); // this is permanent
+ $conn->close();
+ } catch ( DBConnectionError $e ) {
+ return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() );
+ }
+
+ # Open the main DB
+ return $this->getConnection();
+ }
+
+ protected function makeStubDBFile( $dir, $db ) {
$file = DatabaseSqlite::generateFileName( $dir, $db );
if ( file_exists( $file ) ) {
if ( !is_writable( $file ) ) {
@@ -236,13 +279,8 @@ class SqliteInstaller extends DatabaseInstaller {
return Status::newFatal( 'config-sqlite-cant-create-db', $file );
}
}
- // nuke the unused settings for clarity
- $this->setVar( 'wgDBserver', '' );
- $this->setVar( 'wgDBuser', '' );
- $this->setVar( 'wgDBpassword', '' );
- $this->setupSchemaVars();
- return $this->getConnection();
+ return Status::newGood();
}
/**
@@ -280,6 +318,16 @@ class SqliteInstaller extends DatabaseInstaller {
$dir = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgSQLiteDataDir' ) );
return "# SQLite-specific settings
-\$wgSQLiteDataDir = \"{$dir}\";";
+\$wgSQLiteDataDir = \"{$dir}\";
+\$wgObjectCaches[CACHE_DB] = array(
+ 'class' => 'SqlBagOStuff',
+ 'loggroup' => 'SQLBagOStuff',
+ 'server' => array(
+ 'type' => 'sqlite',
+ 'dbname' => 'wikicache',
+ 'tablePrefix' => '',
+ 'flags' => 0
+ )
+);";
}
}
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
index ab5ab7d7..2693be0d 100644
--- a/includes/installer/SqliteUpdater.php
+++ b/includes/installer/SqliteUpdater.php
@@ -137,6 +137,12 @@ class SqliteUpdater extends DatabaseUpdater {
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' ),
+
+ // 1.25
+ array( 'dropTable', 'hitcounter' ),
+ array( 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ),
+ array( 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ),
+ array( 'modifyField', 'filearchive', 'fa_deleted_reason', 'patch-editsummary-length.sql' ),
);
}
@@ -168,11 +174,4 @@ class SqliteUpdater extends DatabaseUpdater {
$this->output( "...fulltext search table appears to be in order.\n" );
}
}
-
- protected function doEnableProfiling() {
- global $wgProfileToDatabase;
- if ( $wgProfileToDatabase === true && !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
- $this->applyPatch( 'patch-profiling.sql', false, 'Add profiling table' );
- }
- }
}
diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php
index f3dba3a7..156606a6 100644
--- a/includes/installer/WebInstaller.php
+++ b/includes/installer/WebInstaller.php
@@ -1163,6 +1163,25 @@ class WebInstaller extends Installer {
} elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
$path = $_SERVER['SCRIPT_NAME'];
}
+ if ( $path === false ) {
+ $this->showError( 'config-no-uri' );
+ return false;
+ }
+
+ return parent::envCheckPath();
+ }
+
+ public function envPrepPath() {
+ parent::envPrepPath();
+ // PHP_SELF isn't available sometimes, such as when PHP is CGI but
+ // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
+ // to get the path to the current script... hopefully it's reliable. SIGH
+ $path = false;
+ if ( !empty( $_SERVER['PHP_SELF'] ) ) {
+ $path = $_SERVER['PHP_SELF'];
+ } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
+ $path = $_SERVER['SCRIPT_NAME'];
+ }
if ( $path !== false ) {
$scriptPath = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
$scriptExtension = $this->getVar( 'wgScriptExtension' );
@@ -1175,14 +1194,8 @@ class WebInstaller extends Installer {
$this->setVar( 'wgLocalStylePath', "$scriptPath/skins" );
$this->setVar( 'wgExtensionAssetsPath', "$scriptPath/extensions" );
$this->setVar( 'wgUploadPath', "$scriptPath/images" );
-
- } else {
- $this->showError( 'config-no-uri' );
-
- return false;
+ $this->setVar( 'wgResourceBasePath', "$scriptPath" );
}
-
- return parent::envCheckPath();
}
/**
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
index 3094d557..0ccdb11a 100644
--- a/includes/installer/WebInstallerOutput.php
+++ b/includes/installer/WebInstallerOutput.php
@@ -133,26 +133,24 @@ class WebInstallerOutput {
'mediawiki.skinning.interface',
);
- if ( file_exists( "$wgStyleDirectory/Vector/Vector.php" ) ) {
+ $resourceLoader = new ResourceLoader();
+
+ if ( file_exists( "$wgStyleDirectory/Vector/skin.json" ) ) {
// 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";
+ $registry = new ExtensionRegistry();
+ $data = $registry->readFromQueue( array(
+ "$wgStyleDirectory/Vector/skin.json" => 1,
+ ) );
+ if ( isset( $data['globals']['wgResourceModules'] ) ) {
+ $resourceLoader->register( $data['globals']['wgResourceModules'] );
+ }
$moduleNames[] = 'skins.vector.styles';
}
$moduleNames[] = 'mediawiki.legacy.config';
- $resourceLoader = new ResourceLoader();
$rlContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( array(
'debug' => 'true',
'lang' => $this->getLanguageCode(),
@@ -163,6 +161,10 @@ class WebInstallerOutput {
foreach ( $moduleNames as $moduleName ) {
/** @var ResourceLoaderFileModule $module */
$module = $resourceLoader->getModule( $moduleName );
+ if ( !$module ) {
+ // T98043: Don't fatal, but it won't look as pretty.
+ continue;
+ }
// Based on: ResourceLoaderFileModule::getStyles (without the DB query)
$styles = array_merge( $styles, ResourceLoader::makeCombinedStyles(
@@ -227,7 +229,7 @@ class WebInstallerOutput {
public function getHeadAttribs() {
return array(
'dir' => $this->getDir(),
- 'lang' => $this->getLanguageCode(),
+ 'lang' => wfBCP47( $this->getLanguageCode() ),
);
}
@@ -296,11 +298,14 @@ class WebInstallerOutput {
href="https://www.mediawiki.org/"
title="Main Page"></a>
</div>
- <div class="portal"><div class="body">
<?php
- echo $this->parent->parse( wfMessage( 'config-sidebar' )->plain(), true );
+ $message = wfMessage( 'config-sidebar' )->plain();
+ foreach ( explode( '----', $message ) as $section ) {
+ echo '<div class="portal"><div class="body">';
+ echo $this->parent->parse( $section, true );
+ echo '</div></div>';
+ }
?>
- </div></div>
</div>
<?php
diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php
index 2e31e413..98f3ae8a 100644
--- a/includes/installer/WebInstallerPage.php
+++ b/includes/installer/WebInstallerPage.php
@@ -796,6 +796,9 @@ class WebInstallerName extends WebInstallerPage {
) ) .
$this->parent->getTextBox( array(
'var' => '_AdminEmail',
+ 'attribs' => array(
+ 'dir' => 'ltr',
+ ),
'label' => 'config-admin-email',
'help' => $this->parent->getHelpBox( 'config-admin-email-help' )
) ) .
@@ -1030,14 +1033,15 @@ class WebInstallerOptions extends WebInstallerPage {
$skins = $this->parent->findExtensions( 'skins' );
$skinHtml = $this->getFieldSetStart( 'config-skins' );
- if ( $skins ) {
- $skinNames = array_map( 'strtolower', $skins );
+ $skinNames = array_map( 'strtolower', $skins );
+ $chosenSkinName = $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
+ if ( $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 ) ),
+ 'value' => $chosenSkinName,
) );
foreach ( $skins as $skin ) {
@@ -1052,7 +1056,9 @@ class WebInstallerOptions extends WebInstallerPage {
'</div>';
}
} else {
- $skinHtml .= $this->parent->getWarningBox( wfMessage( 'config-skins-missing' )->plain() );
+ $skinHtml .=
+ $this->parent->getWarningBox( wfMessage( 'config-skins-missing' )->plain() ) .
+ Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
}
$skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
@@ -1177,7 +1183,7 @@ class WebInstallerOptions extends WebInstallerPage {
) );
$styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
'/mw-config/config-cc.css';
- $iframeUrl = 'http://creativecommons.org/license/?' .
+ $iframeUrl = '//creativecommons.org/license/?' .
wfArrayToCgi( array(
'partner' => 'MediaWiki',
'exit_url' => $exitUrl,
@@ -1284,8 +1290,7 @@ class WebInstallerOptions extends WebInstallerPage {
$retVal = true;
- if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles )
- ) {
+ if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) {
reset( $this->parent->rightsProfiles );
$this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
}
@@ -1461,7 +1466,7 @@ class WebInstallerComplete extends WebInstallerPage {
strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false
) {
// JS appears to be the only method that works consistently with IE7+
- $this->addHtml( "\n<script>jQuery( function () { document.location = " .
+ $this->addHtml( "\n<script>jQuery( function () { location.href = " .
Xml::encodeJsVar( $lsUrl ) . "; } );</script>\n" );
} else {
$this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
diff --git a/includes/installer/i18n/ar.json b/includes/installer/i18n/ar.json
index 7edb97c4..36136a5e 100644
--- a/includes/installer/i18n/ar.json
+++ b/includes/installer/i18n/ar.json
@@ -47,16 +47,20 @@
"config-restart": "نعم، إعادة التشغيل",
"config-env-php": "بي إتش بي $1 مثبت.",
"config-db-type": "نوع قاعدة البيانات:",
+ "config-db-host": "مضيف قاعدة البيانات:",
"config-db-wiki-settings": "حدِّد هذا الويكي",
"config-db-name": "اسم قاعدة البيانات",
+ "config-db-name-oracle": "سكيما قاعدة البيانات:",
"config-db-username": "اسم مستخدم قاعدة البيانات:",
"config-db-password": "كلمة سر قاعدة البيانات:",
+ "config-db-prefix": "بادئة جدول قاعدة البيانات:",
"config-db-port": "منفذ قاعدة البيانات:",
"config-db-schema": "سكيما لميدياويكي",
- "config-type-mysql": "ماي إس كيو إل",
+ "config-type-mysql": "MySQL (أو متوافق)",
"config-type-postgres": "بوستجر إس كيو إل",
"config-type-sqlite": "إس كيو لايت",
"config-type-oracle": "أوراكل",
+ "config-type-mssql": "خادم SQL لميكروسوفت",
"config-header-mysql": "إعدادات MySQL",
"config-header-postgres": "إعدادات PostgreSQL",
"config-header-sqlite": "إعدادات SQLite",
@@ -109,12 +113,14 @@
"config-install-extensions": "متضمنا الامتدادات",
"config-install-database": "إنشاء قاعدة البيانات",
"config-install-schema": "إنشاء السكيما",
+ "config-install-pg-commit": "تنفيذ التغييرات",
"config-install-user": "إنشاء مستخدم قاعدة البيانات",
"config-install-user-alreadyexists": "المستخدم \"$1\" موجود بالفعل",
"config-install-user-create-failed": "إنشاء مستخدم \"$1\" فشل:$2",
"config-install-tables": "إنشاء الجداول",
"config-install-keys": "توليد المفاتيح السرية",
"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 أسئلة متكررة حول ميدياويكي]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce القائمة البريدية الخاصة بإصدار ميدياويكي]"
}
diff --git a/includes/installer/i18n/ast.json b/includes/installer/i18n/ast.json
index df7184f5..62f0f036 100644
--- a/includes/installer/i18n/ast.json
+++ b/includes/installer/i18n/ast.json
@@ -46,15 +46,18 @@
"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-env-hhvm": "HHVM $1 ta instaláu.",
"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-no-db": "¡Nun pudo alcontrase un controlador de base de datos afayadizu! Necesites instalar un controlador de base de datos pa PHP.\n{{PLURAL:$2|Tien sofitu el tipu de base de datos siguiente|Tienen sofitu los tipos de base de datos siguientes}}: $1.\n\nSi compilasti PHP tu mesmu, reconfigúralu con un cliente de base de datos activáu, por exemplu, usando <code>./configure --with-mysqli</code>.\nSi instalasti PHP dende un paquete de Debian o Ubuntu, necesites 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-register-globals-error": "<strong>Error: la opción de PHP <code>[http://php.net/register_globals register_globals]</code> ta activada.\nTien de desactivase pa siguir cola instalación.</strong>\nVisita [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] p'alcontrar ayuda tocante a cómo facelo.",
+ "config-diff3-bad": "Nun s'alcontró GNU diff3.",
+ "config-git": "Alcontróse'l software de control de versiones Git: <code>$1</code>.",
+ "config-git-bad": "Nun s'alcontró el software de control de versiones Git.",
"config-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.",
diff --git a/includes/installer/i18n/av.json b/includes/installer/i18n/av.json
index 0ce76fa0..3568258e 100644
--- a/includes/installer/i18n/av.json
+++ b/includes/installer/i18n/av.json
@@ -4,5 +4,8 @@
"Gazimagomedov"
]
},
- "config-page-options": "Рекъезаби"
+ "config-page-welcome": "ЛъикI щварал МедиаВикиялда!",
+ "config-page-name": "ЦӀар",
+ "config-page-options": "Рекъезаби",
+ "config-page-complete": "ЛъугӀана!"
}
diff --git a/includes/installer/i18n/az.json b/includes/installer/i18n/az.json
index 8688fe4e..8fda8d4d 100644
--- a/includes/installer/i18n/az.json
+++ b/includes/installer/i18n/az.json
@@ -23,7 +23,7 @@
"config-mysql-myisam": "MyISAM",
"config-mysql-utf8": "UTF-8",
"config-ns-generic": "Layihə",
- "config-admin-name": "Sizin adınız:",
+ "config-admin-name": "Sizin istifadəçi adınız:",
"config-admin-password": "Parol:",
"config-admin-email": "E-poçt ünvanı",
"config-license-pd": "İctimai istifadə",
diff --git a/includes/installer/i18n/ba.json b/includes/installer/i18n/ba.json
index cca993ae..e3838e3d 100644
--- a/includes/installer/i18n/ba.json
+++ b/includes/installer/i18n/ba.json
@@ -2,9 +2,28 @@
"@metadata": {
"authors": [
"Haqmar",
- "Seb35"
+ "Seb35",
+ "Рустам Нурыев"
]
},
+ "config-desc": "MediaWiki йөкләүсе",
+ "config-title": "MediaWiki $1 йөкләмеше",
+ "config-information": "Мәғлүмәт",
+ "config-localsettings-key": "Яңыртыу асҡысы:",
+ "config-localsettings-badkey": "Дөрөҫ булмаған асҡыс күрһәттегеҙ",
+ "config-your-language": "Һеҙҙең тел:",
+ "config-back": "← Кире",
+ "config-continue": "Дауам итергә →",
+ "config-page-language": "Тел",
+ "config-page-welcome": "MediaWiki-ға рәхим итегеҙ!",
+ "config-page-name": "Исем",
+ "config-page-options": "Көйләүҙәр",
+ "config-page-complete": "Тамам!",
+ "config-page-readme": "Мине уҡы",
+ "config-page-releasenotes": "Өлгө тураһында мәғлүмәт",
+ "config-page-copying": "Рөхсәтнәмә",
+ "config-page-upgradedoc": "Яңыртыу",
+ "config-restart": "Эйе, яңынан башларға",
"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/be-tarask.json b/includes/installer/i18n/be-tarask.json
index 0aeae7fc..c131a781 100644
--- a/includes/installer/i18n/be-tarask.json
+++ b/includes/installer/i18n/be-tarask.json
@@ -56,7 +56,7 @@
"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-no-db": "Немагчыма знайсьці адпаведны драйвэр базы зьвестак. Вам неабходна ўсталяваць драйвэр базы зьвестак для PHP.\n{{PLURAL:$2|Падтрымліваецца наступны тып базы|Падтрымліваюцца наступныя тыпы базаў}} зьвестак: $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] дзеля дапамогі, як зрабіць гэта.",
diff --git a/includes/installer/i18n/bg.json b/includes/installer/i18n/bg.json
index 32e4c397..51fe74f5 100644
--- a/includes/installer/i18n/bg.json
+++ b/includes/installer/i18n/bg.json
@@ -3,7 +3,8 @@
"authors": [
"DCLXVI",
"아라",
- "StanProg"
+ "StanProg",
+ "Vodnokon4e"
]
},
"config-desc": "Инсталатор на МедияУики",
@@ -48,11 +49,12 @@
"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Ако сайтът е с голям трафик, препоръчително е запознаването с [//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-no-db": "Не може да бъде открит подходящ драйвер за база данни! Необходимо е да инсталирате драйвер за база данни за PHP.\nПоддържат се следните типове базни данни: $1.\n\nАко сами сте компилирали PHP, преконфигурирайте го с включен клиент за база данни, например чрез използване на <code>./configure --with-mysql</code>.\nАко сте инсталирали PHP от пакет за Debian или Ubuntu, необходимо е също така да инсталирате и модула <code>php5-mysql</code>.",
"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Инсталацията на МедияУики е невъзможна докато тази настройка не бъде изключена.",
@@ -69,6 +71,7 @@
"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-git-bad": "Не е намерен софтуер за контрол на версиите Git.",
"config-imagemagick": "Открит е ImageMagick: <code>$1</code>.\nПреоразмеряването на картинки ще бъде включено ако качването на файлове бъде разрешено.",
"config-gd": "Открита е вградена графичната библиотека GD.\nАко качването на файлове бъде включено, ще бъде включена възможността за преоразмеряване на картинки.",
"config-no-scaling": "Не са открити библиотеките GD или ImageMagick.\nПреоразмеряването на картинки ще бъде изключено.",
@@ -207,7 +210,7 @@
"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-license-help": "Много публични уикита поставят всички приноси под [http://freedomdefined.org/Definition/Bg свободен лиценз].\nТова помага за създаването на усещане за общност и насърчава дългосрочните приноси. \nТова не е необходимо като цяло за частно или корпоративно уики.\n\nАко искате да използвате текстове от Уикипедия, и искате Уикипедия да може да приема текстове, копирани от вашето уики, трябва да изберете лиценз <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nЛицензът за свободна документация на GNU е старият лиценз на съдържанието на Уикипедия.\nТой все още е валиден лиценз, но някои негови условия са трудни за разбиране и правят по-сложни повторното използване и интерпретацията.",
"config-email-settings": "Настройки за е-поща",
"config-enable-email": "Разрешаване на изходящи е-писма",
"config-enable-email-help": "За да работят възможностите за използване на е-поща, необходимо е [http://www.php.net/manual/en/mail.configuration.php настройките за поща на PHP] да бъдат конфигурирани правилно.\nАко няма да се използват услугите за е-поща в уикито, те могат да бъдат изключени тук.",
@@ -248,6 +251,8 @@
"config-extensions": "Разширения",
"config-extensions-help": "Разширенията от списъка по-горе бяха открити в директорията <code>./extensions</code>.\n\nВъзможно е те да изискват допълнително конфигуриране, но сега могат да бъдат включени.",
"config-skins": "Облици",
+ "config-skins-help": "По-горе са посочени облиците, които са открити във вашата директория <code>./skins</code>. Необходимо е да изберете поне един, който да се използва по подразбиране.",
+ "config-skins-use-as-default": "Използване на този облик по подразбиране",
"config-install-alreadydone": "'''Предупреждение:''' Изглежда вече сте инсталирали МедияУики и се опитвате да го инсталирате отново.\nПродължете към следващата страница.",
"config-install-begin": "Инсталацията на МедияУики ще започне след натискане на бутона „{{int:config-continue}}“.\nВ случай, че е необходимо да се направят промени, използва се бутона „{{int:config-back}}“.",
"config-install-step-done": "готово",
diff --git a/includes/installer/i18n/bgn.json b/includes/installer/i18n/bgn.json
new file mode 100644
index 00000000..196f1c47
--- /dev/null
+++ b/includes/installer/i18n/bgn.json
@@ -0,0 +1,26 @@
+{
+ "@metadata": {
+ "authors": [
+ "Baloch Afghanistan",
+ "Ibrahim khashrowdi"
+ ]
+ },
+ "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-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-ns-generic": "پروژه",
+ "config-ns-other-default": "نی ویکی"
+}
diff --git a/includes/installer/i18n/bn.json b/includes/installer/i18n/bn.json
index 212c5995..0aecb705 100644
--- a/includes/installer/i18n/bn.json
+++ b/includes/installer/i18n/bn.json
@@ -4,7 +4,8 @@
"Bellayet",
"Wikitanvir",
"Aftab1995",
- "Tauhid16"
+ "Tauhid16",
+ "Aftabuzzaman"
]
},
"config-desc": "মিডিয়াউইকির জন্য ইন্সটলার",
@@ -46,7 +47,7 @@
"config-db-name": "উপাত্তসংগ্রহশালা নামঃ",
"config-db-install-account": "ইন্সটলের জন্য ব্যবহারকারী অ্যাকাউন্ট",
"config-db-username": "ডেটাবেজের ব্যবহারকারী নাম:",
- "config-db-password": "ডেটাবেজের শব্দচাবি:",
+ "config-db-password": "ডেটাবেজের পাসওয়ার্ড:",
"config-db-username-empty": "আপনাকে অবশ্যই \"{{int:config-db-username}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
"config-db-wiki-account": "সাধারণ অভিযানের জন্য ব্যবহারকারী একাউন্ট",
"config-db-prefix": "উপাত্তশালা ছক প্রিফিক্স:",
@@ -66,7 +67,7 @@
"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-connection-error": "$1।\n\n\nদয়া করে প্রস্তাবকারী, ব্যবহারকারী নাম ও পাসওয়ার্ড দেখুন এবং পুনরায় চেষ্টা করুন।",
"config-mysql-engine": "সংরক্ষণ ইঞ্জিন:",
"config-mysql-innodb": "ইনোডিবি",
"config-mysql-myisam": "মাইআইএসএএম",
@@ -83,11 +84,11 @@
"config-ns-other-default": "মাইউইকি",
"config-admin-box": "প্রশাসক অ্যাকাউন্ট",
"config-admin-name": "আপনার ব্যবহারকারী নাম:",
- "config-admin-password": "শব্দচাবি:",
- "config-admin-password-confirm": "শব্দচাবি আবারও প্রবেশ করান:",
+ "config-admin-password": "পাসওয়ার্ড:",
+ "config-admin-password-confirm": "পাসওয়ার্ড আবারও প্রবেশ করান:",
"config-admin-name-blank": "একটি প্রশাসক ব্যবহারকারী নাম প্রবেশ করান",
"config-admin-password-blank": "প্রশাসক অ্যাকাউন্টের জন্য পাসওয়ার্ড প্রবেশ করান।",
- "config-admin-password-mismatch": "আপনি যে দুটি শব্দচাবি দিয়েছেন তারা পরস্পর মেলেনি।",
+ "config-admin-password-mismatch": "আপনি যে দুটি পাসওয়ার্ড দিয়েছেন তারা পরস্পর মেলেনি।",
"config-admin-email": "ইমেইল ঠিকানা:",
"config-optional-continue": "আরও প্রশ্ন জিজ্ঞেস করুন।",
"config-optional-skip": "আমি ইতিমধ্যেই বিরক্ত হয়ে গেছি, উইকিটি ইন্সটল করো।",
diff --git a/includes/installer/i18n/bs.json b/includes/installer/i18n/bs.json
index 28b14fae..668fbff7 100644
--- a/includes/installer/i18n/bs.json
+++ b/includes/installer/i18n/bs.json
@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
- "CERminator"
+ "CERminator",
+ "Palapa"
]
},
"config-desc": "Instalacija za MediaWiki",
@@ -9,6 +10,7 @@
"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-localsettings-badkey": "Ključ koji ste dali je pogrešan.",
"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:",
@@ -31,12 +33,14 @@
"config-page-releasenotes": "Bilješke izdanja",
"config-page-copying": "Kopiram",
"config-page-upgradedoc": "Nadograđujem",
+ "config-page-existingwiki": "Postojeća wiki",
"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-env-hhvm": "HHVM $1 je instaliran.",
+ "config-no-db": "Nije mogao biti pronađen pogodan driver za bazu podataka! Morate instalirati driver baze podataka za PHP.\nSljedeće vrste baza podataka su podržane: $1.\n\nAko se sami kompajlirali PHP, podesite ga sa omogućenim klijentom baze podataka, koristeći naprimjer, <code>./configure --with-mysqli</code>.\nAko ste instalirali PHP iz Debian ili Ubuntu paketa, tada morate instalirati, naprimjer, i paket <code>php5-mysql</code>.",
"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",
@@ -46,14 +50,37 @@
"config-db-wiki-settings": "Identificiraj ovu wiki",
"config-db-name": "Naziv baze podataka:",
"config-db-name-oracle": "Šema baze podataka:",
+ "config-db-username": "Korisničko ime baze podataka:",
+ "config-db-password": "Pasvord baze podataka:",
+ "config-db-port": "Port baze podataka:",
"config-header-mysql": "Postavke MySQL",
"config-header-postgres": "Postavke PostgreSQL",
"config-header-sqlite": "Postavke SQLite",
"config-header-oracle": "Postavke Oracle",
+ "config-header-mssql": "Postavke za Microsoft SQL Server",
"config-invalid-db-type": "Nevaljana vrsta baze podataka",
+ "config-missing-db-name": "Morate unijeti vrijednost za \"{{int:config-db-name}}\".",
"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-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "Binarni",
+ "config-site-name": "Ime wiki:",
+ "config-ns-other-default": "MyWiki",
+ "config-admin-name": "Vaše korisničko ime:",
"config-admin-password": "Šifra:",
+ "config-admin-password-confirm": "Ponovno unesite pasvord:",
+ "config-admin-email": "E-mail adresa:",
+ "config-profile-wiki": "Otvori wiki",
+ "config-profile-private": "Privatna wiki",
+ "config-license-pd": "Javno vlasništvo",
+ "config-logo": "Logo URL:",
+ "config-cc-again": "Odaberi ponovno...",
+ "config-install-step-done": "završeno",
+ "config-install-step-failed": "neuspješno",
+ "config-install-extensions": "Uključujući ekstenzije",
+ "config-install-tables": "Kreiranje tabela",
+ "config-help": "pomoć",
+ "config-help-tooltip": "klikni za proširenje",
"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/ca.json b/includes/installer/i18n/ca.json
index 3f1fcfc0..6d28a582 100644
--- a/includes/installer/i18n/ca.json
+++ b/includes/installer/i18n/ca.json
@@ -5,7 +5,11 @@
"පසිඳු කාවින්ද",
"Kippelboy",
"Toniher",
- "Fitoschido"
+ "Fitoschido",
+ "Jmarchn",
+ "Alvaro Vidal-Abarca",
+ "ESM",
+ "Xavier Dengra"
]
},
"config-desc": "L'instal·lador del MediaWiki",
@@ -19,6 +23,8 @@
"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-session-expired": "Les dades de la vostra sessió sembla que han caducat.\nLes sessions estan configurades per a un temps de $1.\nPodeu augmentar-lo posant <code>session.gc_maxlifetime</code> en php.ini.\nReprengueu el procés d'instal·lació.",
+ "config-no-session": "Les dades de la vostra sessió s'han perdut!\nComprovar el vostre php.ini i assegureu-vos que <code>session.save_path</code> està assignat a un directori apropiat.",
"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:",
@@ -43,12 +49,18 @@
"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-copyright": "=== Drets d'autor i condicions ===\n\n$1\n\nAquest programa és de programari lliure; podeu redistribuir-lo i/o modificar-lo sota les condicions de la Llicència Pública General GNU com es publicada per la Free Software Foundation; qualsevol versió 2 de la llicència, o (opcionalment) qualsevol versió posterior.\n\nAquest programa és distribueix amb l'esperança que serà útil, però <strong>sense cap garantia</strong>; sense ni tan sols la garantia implícita de <strong>\ncomerciabilitat</strong> o <strong>idoneïtat per a un propòsit particular</strong>.\nConsulteu la Llicència Pública General GNU, per a més detalls.\n\nHauríeu d'haver rebut <doclink href=\"Copying\">una còpia de la Llicència Pública General GNU</doclink> amb aquest programa; si no, escriviu a la Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA o [http://www.gnu.org/copyleft/gpl.html per llegir-lo en línia].",
+ "config-sidebar": "* [//www.mediawiki.org la Pàgina d'inici]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Guia de l'Usuari]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Guia de l'Administrador]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ]\n----\n* <doclink href=Readme>Llegeix-me</doclink>\n* <doclink href=ReleaseNotes>Notes de la versió</doclink>\n* <doclink href=Còpia>Còpia</doclink>\n* <doclink href=UpgradeDoc>Actualització</doclink>",
"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-env-hhvm": "L’HHVM $1 és instal·lat.",
+ "config-unicode-using-intl": "S'utilitza l'[http://pecl.php.net/intl extensió intl PECL] per a la normalització de l'Unicode.",
"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-xcache": "[http://xcache.lighttpd.net/ XCache] està instal·lat",
"config-apc": "L’[http://www.php.net/apc APC] està instal·lat",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] 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.",
@@ -60,6 +72,7 @@
"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-host-oracle": "TNS 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.",
@@ -67,7 +80,11 @@
"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-password-empty": "Si us plau, introduïu una contrasenya pel nou usuari de la base de dades $1. Tot i que es poden crear usuaris sense contrasenyes, no és segur.",
"config-db-username-empty": "Heu d'introduir un valor per a «{{int:config-db-username}}»",
+ "config-db-install-username": "Introduïu el nom d'usuari que s'utilitzarà per connectar a la base de dades durant el procés d'instal·lació. Aquest no és el nom d'usuari de MediaWiki, és el nom d'usuari de la vostra base de dades.",
+ "config-db-install-password": "Introduïu la contrasenya que s'utilitzarà per connectar a la base de dades durant el procés d'instal·lació. Aquesta no és la contrasenya del vostre compte a MediaWiki, és la contrasenya de la vostra base de dades.",
+ "config-db-install-help": "Introduïu el nom d'usuari i la contrasenya que s'empraran per connectar a la base de dades durant el procés d'instal·lació.",
"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.",
@@ -76,6 +93,7 @@
"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-charset-mysql4": "MySQL 4.0 compatible cap enrere amb 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:",
@@ -86,6 +104,7 @@
"config-oracle-temp-ts": "Espai de taules temporal:",
"config-type-mysql": "MySQL (o compatible)",
"config-type-mssql": "Microsoft SQL Server",
+ "config-support-info": "MediaWiki és compatible amb els següents sistemes de bases de dades:\n$1\nSi el sistema de bases de dades que intenteu utilitzar no apareix a la llista, seguiu les instruccions enllaçades més amunt per habilitar el suport.",
"config-header-mysql": "Paràmetres de MySQL",
"config-header-postgres": "Paràmetres del PostgreSQL",
"config-header-sqlite": "Paràmetres de l'SQLite",
@@ -95,13 +114,21 @@
"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-connection-error": "$1.\n\nComproveu el servidor central, el nom d'usuari i la contrasenya i torneu-ho a provar.",
+ "config-db-sys-create-oracle": "L'instal·lador només accepta emprar un compte SYSDBA per a la creació d'un nou compte.",
"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-postgres-old": "Cal el PostgreSQL $1 o posterior. Teniu el $2.",
+ "config-mssql-old": "Cal utilitzar el Microsoft SQL Server $1 o posterior. Teniu la versió $2.",
+ "config-sqlite-mkdir-error": "S'ha produït un error en crear el directori de dades «$1».\nComproveu la ubicació i torneu-ho a provar.",
+ "config-sqlite-dir-unwritable": "No s'ha pogut escriure al directori «$1».\nCanvieu els seus permisos per tal que el servidor web pugui escriure-hi i torneu-ho a provar.",
+ "config-sqlite-connection-error": "$1. \n\nComproveu el directori de dades i el nom de la base de dades a continuació i torneu-ho a provar.",
"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-help": "Seleccioneu el nom d'usuari i la contrasenya que el servidor web utilitzarà per a connectar-se al servidor de base de dades durant el funcionament normal del wiki.",
"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.",
@@ -115,6 +142,7 @@
"config-mssql-sqlauth": "Autenticació de l’SQL Server",
"config-mssql-windowsauth": "Autenticació del Windows",
"config-site-name": "Nom del wiki:",
+ "config-site-name-help": "Això apareixerà en la barra de títol del navegador i en altres llocs diferents.",
"config-site-name-blank": "Introduïu un nom per al lloc.",
"config-project-namespace": "Espai de noms del projecte:",
"config-ns-generic": "Projecte",
@@ -131,6 +159,8 @@
"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-user": "S'ha produït un error intern en crear un administrador amb el nom «<nowiki>$1</nowiki>».",
+ "config-admin-error-password": "S'ha produït un error intern en definir una contrasenya per a l'administrador «<nowiki>$1</nowiki>»: <pre>$2</pre>",
"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.",
@@ -142,7 +172,13 @@
"config-profile-private": "Wiki privat",
"config-license": "Copyright i llicència:",
"config-license-none": "Sense llicència al peu de pàgina",
+ "config-license-cc-by-sa": "Creative Commons Reconeixement-CompartirIgual",
+ "config-license-cc-by": "Creative Commons Reconeixement",
+ "config-license-cc-by-nc-sa": "Creative Commons Reconeixement-NoComercial-CompartirIgual",
+ "config-license-cc-0": "Creative Commons Zero (Domini Públic)",
+ "config-license-gfdl": "GNU Free Documentation License 1.3 o posterior",
"config-license-pd": "Domini públic",
+ "config-license-cc-choose": "Selecció d'una llicència personalitzada de Creative Commons",
"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.",
@@ -154,6 +190,7 @@
"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-upload-deleted-help": "Trieu un directori on arxivar els fitxers suprimits.\nIdealment no hauria de ser accessible des del web.",
"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.",
@@ -163,9 +200,16 @@
"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-cache-memcached": "Utilitza Memcached (requereix una instal·lació i configuració addicionals)",
"config-memcached-servers": "Servidors de Memcache:",
+ "config-memcache-badip": "Heu introduït una adreça IP no vàlida per al Memcached: $1.",
+ "config-memcache-noport": "No heu especificat un port per utilitzar el servidor Memcached: $1.\nSi no coneixeu el port, per defecte és 11211.",
"config-extensions": "Extensions",
"config-skins": "Aparences",
+ "config-skins-use-as-default": "Utilitza aquest tema per defecte",
+ "config-skins-missing": "No s'ha trobat cap tema; MediaWiki utilitzarà el tema per defecte fins que hi instal·leu alguns adequats.",
+ "config-skins-must-enable-some": "Heu de triar com a mínim un tema per habilitar.",
+ "config-skins-must-enable-default": "Cal habilitar el tema triat per defecte.",
"config-install-step-done": "fet",
"config-install-step-failed": "ha fallat",
"config-install-extensions": "S'estan incloent les extensions",
@@ -174,6 +218,8 @@
"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-pg-no-plpgsql": "Necessiteu instal·lar l'idioma PL/pgSQL a la base de dades $1",
+ "config-pg-no-create-privs": "El compte que heu especificat per a la instal·lació no té suficients permisos per crear un compte.",
"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",
@@ -188,6 +234,7 @@
"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-updates": "Evita que s'executin actualitzacions no necessàries",
"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.",
diff --git a/includes/installer/i18n/ckb.json b/includes/installer/i18n/ckb.json
index d0db0849..202edeff 100644
--- a/includes/installer/i18n/ckb.json
+++ b/includes/installer/i18n/ckb.json
@@ -6,11 +6,13 @@
"Muhammed taha"
]
},
+ "config-desc": "دامەزرێنەرەکە بۆ میدیاویکی",
+ "config-title": "دامەزرانی میدیاویکی $1",
"config-information": "زانیاری",
"config-your-language": "زمانەکەت:",
"config-wiki-language": "زمانی ویکی:",
"config-back": "→ گەڕانەوە",
- "config-continue": "بەردەوامبوون ←",
+ "config-continue": "بەردەوام بە ←",
"config-page-language": "زمان",
"config-page-welcome": "بەخێربێیت بۆ میدیاویکی!",
"config-page-dbconnect": "پەیوەندی دەکات بەبنکەی زانیارییەکان",
@@ -26,15 +28,26 @@
"config-page-upgradedoc": "نوێدەکرێتەوە",
"config-page-existingwiki": "ویکی پێشوو",
"config-restart": "بەڵێ، دەستی پێ بکەرەوە",
- "config-env-php": "PHP $1 دابەزێندرا.",
- "config-env-php-toolow": "PHP $1 دابەزێندرا.\nھەرچۆنێک بێت میدیاویکی پێویستی بە PHP $2 یان بەرزتر ھەیە.",
+ "config-env-php": "PHP $1 دامەزراوە.",
+ "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-db-install-account": "ھەژماری بەکارھێنەری بۆ دامەزراندن",
"config-db-username": "ناوی بەکارھێنەری بنکەدراوە:",
"config-db-password": "تێپەڕوشەی بنکەدراوە",
"config-site-name": "ناوی ویکی:",
"config-ns-generic": "پرۆژە",
+ "config-admin-name": "ناوی بەکارھێنەرییەکەت:",
"config-admin-password": "تێپەڕوشە:",
+ "config-admin-password-confirm": "دووبارە تێپەڕوشە:",
"config-admin-email": "ناونیشانی ئیمەیل:",
+ "config-profile-wiki": "ویکیی کراوە",
+ "config-profile-no-anon": "دروستکردنی ھەژمارە پێویستە",
+ "config-profile-fishbowl": "تەنھا دەستکاریکەری ڕێگەپێدراوە",
+ "config-license-pd": "پاوانی گشتی",
+ "config-email-settings": "ڕێکخستنەکانی ئیمەیڵ",
"config-install-step-done": "کرا",
"config-help": "یارمەتی",
"mainpagetext": "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
diff --git a/includes/installer/i18n/cs.json b/includes/installer/i18n/cs.json
index a783cfe2..da2de842 100644
--- a/includes/installer/i18n/cs.json
+++ b/includes/installer/i18n/cs.json
@@ -57,7 +57,7 @@
"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-no-db": "Nepodařilo se nalézt vhodný databázový ovladač! Musíte nainstalovat databázový ovladač pro PHP.\n{{PLURAL:$2|Je podporován následující typ databáze|Jsou 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 <code>php5-mysql</code>.",
"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].",
diff --git a/includes/installer/i18n/cv.json b/includes/installer/i18n/cv.json
index adf128e8..9959f291 100644
--- a/includes/installer/i18n/cv.json
+++ b/includes/installer/i18n/cv.json
@@ -1,9 +1,17 @@
{
"@metadata": {
"authors": [
- "Seb35"
+ "Seb35",
+ "Chuvash2014"
]
},
+ "config-information": "Информаци",
+ "config-your-language": "Сирĕн чĕлхӳ:",
+ "config-wiki-language": "Вики чĕлхе:",
+ "config-back": "← Кутăн",
+ "config-continue": "Малалла →",
+ "config-page-language": "Чĕлхе",
+ "config-page-name": "Ят",
"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
index 5ead1dca..ff7afaac 100644
--- a/includes/installer/i18n/cy.json
+++ b/includes/installer/i18n/cy.json
@@ -7,6 +7,9 @@
]
},
"config-desc": "Y gosodwr ar gyfer MediaWiki",
+ "config-title": "Gosod MediaWiki $1",
+ "config-information": "Gwybodaeth",
+ "config-localsettings-upgrade": "Rydym wedi canfod ffeil <code>LocalSettings.php</code>.\nI uwchraddio'r gosodiad yma, rhowch fanylion y<code>$wgUpgradeKey</code> yn y blwch isod.\nFe'i cewch yn <code>LocalSettings.php</code>.",
"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/de.json b/includes/installer/i18n/de.json
index f329db9b..b348a1b3 100644
--- a/includes/installer/i18n/de.json
+++ b/includes/installer/i18n/de.json
@@ -13,7 +13,8 @@
"아라",
"Se4598",
"Suriyaa Kudo",
- "Das Schäfchen"
+ "Das Schäfchen",
+ "Florian"
]
},
"config-desc": "Das MediaWiki-Installationsprogramm",
@@ -23,7 +24,7 @@
"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-upgrade-key-missing": "Eine MediaWiki-Installation wurde gefunden.\nUm die vorhandene Installation aktualisieren zu können, muss die unten angegebene Codezeile an das Ende der Datei <code>LocalSettings.php</code> 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",
@@ -54,16 +55,15 @@
"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-sidebar": "* [//www.mediawiki.org/wiki/MediaWiki/de Website von MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents/de Benutzer­anleitung]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents/de Administratoren­anleitung]\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>Versions­informationen</doclink>\n* <doclink href=Copying>Lizenz­bestimmungen</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-no-db": "Es konnte kein adäquater Datenbanktreiber gefunden werden. Es muss daher ein Datenbanktreiber für PHP installiert werden.\n{{PLURAL:$2|Das folgende Datenbanksystem wird|Die 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.",
@@ -112,7 +112,7 @@
"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-password-empty": "Bitte ein Passwort für den neuen Datenbankbenutzer angeben: $1.\nObwohl es möglich ist, Datenbankbenutzer ohne Passwort anzulegen, so ist dies 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.",
@@ -141,12 +141,12 @@
"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-support-info": "MediaWiki unterstützt die folgenden Datenbanksysteme:\n\n$1\n\nSofern unterhalb nicht das Datenbanksystem angezeigt wird, das verwendet werden soll, muss dieses noch verfügbar gemacht werden. Oben ist zu jedem unterstützten Datenbanksystem ein Link zur entsprechenden Anleitung vorhanden.",
+ "config-dbsupport-mysql": "* [{{int:version-db-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. ([https://www.php.net/manual/en/mysqli.installation.php Anleitung zur Kompilierung von PHP mit MySQL-Unterstützung])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] ist ein beliebtes Open-Source-Datenbanksystem und eine Alternative zu MySQL. Es gibt allerdings einige kleinere Implementierungsfehler, so dass von der Nutzung in einer Produktivumgebung abgeraten wird. ([https://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 ([https://www.php.net/manual/de/pdo.installation.php Anleitung zur Kompilierung von PHP mit SQLite-Unterstützung], verwendet PHP Data Objects (PDO))",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] ist eine kommerzielle Unternehmensdatenbank ([https://www.php.net/manual/en/oci8.installation.php Anleitung zur Kompilierung von PHP mit OCI8-Unterstützung])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] ist eine gewerbliche Unternehmensdatenbank für Windows. ([https://www.php.net/manual/de/sqlsrv.installation.php Anleitung zur Kompilierung von PHP mit SQLSRV-Unterstützung])",
"config-header-mysql": "MySQL-Einstellungen",
"config-header-postgres": "PostgreSQL-Einstellungen",
"config-header-sqlite": "SQLite-Einstellungen",
diff --git a/includes/installer/i18n/diq.json b/includes/installer/i18n/diq.json
index 843fe2f3..2cc85cea 100644
--- a/includes/installer/i18n/diq.json
+++ b/includes/installer/i18n/diq.json
@@ -42,7 +42,7 @@
"config-ns-other": "Zewbi (keyfiyo)",
"config-ns-other-default": "MyWiki",
"config-admin-box": "Hesabê Administratori",
- "config-admin-name": "Namey karberdé to:",
+ "config-admin-name": "Nameyê şımayê karberi:",
"config-admin-password": "Parola:",
"config-admin-password-confirm": "Fına parola:",
"config-admin-email": "Adresa e-postey:",
diff --git a/includes/installer/i18n/el.json b/includes/installer/i18n/el.json
index 325ee986..27b930a7 100644
--- a/includes/installer/i18n/el.json
+++ b/includes/installer/i18n/el.json
@@ -37,7 +37,6 @@
"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": "Τύπος βάσης δεδομένων:",
@@ -45,11 +44,13 @@
"config-db-host-oracle": "Βάση δεδομένων TNS:",
"config-db-wiki-settings": "Αναγνώριση αυτού του wiki",
"config-db-name": "Όνομα βάσης δεδομένων:",
+ "config-db-name-oracle": "Σχήμα βάσης δεδομένων:",
"config-db-install-account": "Λογαριασμός χρήστη για την εγκατάσταση",
"config-db-username": "Όνομα χρήστη βάσης δεδομένων:",
"config-db-password": "Κωδικός πρόσβασης βάσης δεδομένων:",
"config-db-wiki-account": "Λογαριασμός χρήστη για κανονική λειτουργία",
"config-charset-mysql5-binary": "MySQL 4.1/5.0 δυαδικό",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
"config-db-port": "Θύρα βάσης δεδομένων:",
"config-header-mysql": "Ρυθμίσεις MySQL",
"config-header-postgres": "Ρυθμίσεις PostgreSQL",
@@ -74,6 +75,7 @@
"config-ns-generic": "Εγχείρημα",
"config-ns-site-name": "Ίδιο με το όνομα του wiki: $1",
"config-ns-other": "Άλλο (προσδιορίστε)",
+ "config-ns-other-default": "ΤοWikiμου",
"config-admin-box": "Λογαριασμός διαχειριστή",
"config-admin-name": "Το όνομα χρήστη σας:",
"config-admin-password": "Κωδικός πρόσβασης:",
@@ -88,18 +90,26 @@
"config-profile-no-anon": "Απαιτείται η δημιουργία λογαριασμού",
"config-profile-fishbowl": "Εξουσιοδοτημένοι συντάκτες μόνο",
"config-profile-private": "Ιδιωτικό wiki",
+ "config-license-pd": "Κοινό Κτήμα",
"config-license-cc-choose": "Επιλέξτε μια προσαρμοσμένη άδεια Creative Commons",
"config-email-settings": "Ρυθμίσεις ηλεκτρονικού ταχυδρομείου",
"config-email-usertalk": "Ενεργοποίηση ειδοποίησης σελίδας συζήτησης χρήστη",
"config-email-auth": "Ενεργοποίηση ταυτοποίησης μέσω ηλεκτρονικού ταχυδρομείου",
"config-upload-settings": "Ανέβασμα εικόνων και άλλων αρχείων",
"config-upload-enable": "Ενεργοποιήστε το ανέβασμα αρχείων",
+ "config-upload-deleted": "Καταλόγος για διαγραφέντα αρχεία:",
"config-logo": "Διεύθυνση URL λογότυπου:",
+ "config-instantcommons": "Ενεργοποίηση Instant Commons",
"config-cc-again": "Επιλέξτε ξανά...",
"config-advanced-settings": "Προηγμένες ρυθμίσεις παραμέτρων",
"config-extensions": "Επεκτάσεις",
+ "config-skins": "Θέματα εμφάνισης",
+ "config-skins-help": "Τα θέματα εμφάνισης που αναφέρονται παραπάνω εντοπίστηκαν στον κατάλογο <code>./skins</code>. Πρέπει να ενεργοποιήσετε τουλάχιστον ένα και να επιλέξτε ποιο θα είναι το προεπιλεγμένο.",
+ "config-skins-use-as-default": "Χρήση αυτού του θέματος εμφάνισης ως προεπιλογή",
+ "config-skins-must-enable-default": "Το θέμα εμφάνισης που επιλέχθηκε ως προεπιλεγμένο πρέπει να είναι ενεργοποιημένο.",
"config-install-step-done": "έγινε",
"config-install-step-failed": "απέτυχε",
+ "config-install-database": "Ρύθμιση βάσης δεδομένων",
"config-install-user-alreadyexists": "Ο χρήστης \"$1\" υπάρχει ήδη",
"config-install-tables": "Γίνεται δημιουργία πινάκων",
"config-install-tables-failed": "<strong>Σφάλμα:</strong>Η δημιουργία πινάκων απέτυχε με το ακόλουθο μήνυμα λάθους: $1",
diff --git a/includes/installer/i18n/eml.json b/includes/installer/i18n/eml.json
new file mode 100644
index 00000000..37fefefa
--- /dev/null
+++ b/includes/installer/i18n/eml.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gloria sah"
+ ]
+ },
+ "config-information": "Infurmasiòun",
+ "config-your-language": "La tó lengva:",
+ "config-page-language": "Lengva",
+ "config-charset-mysql5-binary": "binàri MySQL 4.1/5.0",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UTF-8",
+ "config-admin-password-mismatch": "El dó paró li cêv 't ê pruvê i n'vàn mia bèin.",
+ "config-admin-email": "Indirìs e-mail:",
+ "config-optional-continue": "Edmànd-em de piò.",
+ "config-license-cc-by": "Atribusiòun Creative Commons"
+}
diff --git a/includes/installer/i18n/en-gb.json b/includes/installer/i18n/en-gb.json
index 6b9f5901..a79282a2 100644
--- a/includes/installer/i18n/en-gb.json
+++ b/includes/installer/i18n/en-gb.json
@@ -1,9 +1,13 @@
{
"@metadata": {
"authors": [
- "Shirayuki"
+ "Shirayuki",
+ "Caliburn"
]
},
+ "config-desc": "The installer for MediaWiki",
+ "config-title": "MediaWiki $1 installation",
+ "config-information": "Information",
"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.",
diff --git a/includes/installer/i18n/en.json b/includes/installer/i18n/en.json
index 1e1c2da7..c19e3ee6 100644
--- a/includes/installer/i18n/en.json
+++ b/includes/installer/i18n/en.json
@@ -45,11 +45,10 @@
"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-no-db": "Could not find a suitable database driver! You need to install a database driver for PHP.\nThe following database {{PLURAL:$2|type is|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.",
@@ -58,7 +57,7 @@
"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-xml-bad": "PHP's XML module is missing.\nMediaWiki requires functions in this module and will not work in this configuration.\nYou may need to install the php-xml RPM 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.",
@@ -70,7 +69,7 @@
"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-mod-security": "<strong>Warning:</strong> Your web server has [http://modsecurity.org/ mod_security]/mod_security2 enabled. Many common configurations of this will cause problems for MediaWiki and other software that allows users to post arbitrary content.\nIf possible, this should be disabled. Otherwise, refer to [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.",
@@ -151,7 +150,7 @@
"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-name-help": "Choose a name that identifies your wiki.\nDo not use spaces or hyphens.\nThis will be used for the SQLite data filename.",
"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.",
diff --git a/includes/installer/i18n/es.json b/includes/installer/i18n/es.json
index ee1a2f46..a9836514 100644
--- a/includes/installer/i18n/es.json
+++ b/includes/installer/i18n/es.json
@@ -25,19 +25,20 @@
"Miguel2706",
"Macofe",
"AVIADOR",
- "FuzzyDice"
+ "FuzzyDice",
+ "Legoktm"
]
},
"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-upgrade": "Se ha encontrado un archivo <code>LocalSettings.php</code>.\nPara actualizar esta instalación, escribe 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, en su lugar ejecuta <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-upgrade-key-missing": "Se ha detectado una instalación existente de MediaWiki.\nPara actualizar la instalación, añade la siguiente línea al final de tu <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.\nCambia <code>LocalSettings.php</code> para que esta variable quede establecida y haz 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>. Corrige estos ajustes e inténtalo 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.",
@@ -59,10 +60,10 @@
"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-copying": "Copia",
+ "config-page-upgradedoc": "Actualización",
"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-help-restart": "¿Deseas borrar todos los datos guardados que has escrito y reiniciar el proceso de instalación?",
"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].",
@@ -71,69 +72,68 @@
"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-unicode-pure-php-warning": "<strong>Advertencia:</strong> la [http://pecl.php.net/intl extensión intl] no está disponible para efectuar la normalización Unicode. Se utilizará la implementación más lenta en PHP puro.\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": "<strong>Warning:</strong> la versión instalada del contenedor de normalización Unicode usa una versión antigua 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 se encontró un controlador adecuado para la base de datos. Necesitas instalar un controlador de base de datos para PHP.\n{{PLURAL:$2|El siguiente gestor de bases de datos está soportado|Los siguientes 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": "<strong>Advertencia:</strong> tienes SQLite $1, que es inferior a la mínima versión requerida: $2. SQLite no estará disponible.",
+ "config-no-fts3": "<strong>Advertencia:</strong> 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>\nVéase [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-safe-mode": "<strong>Advertencia:</strong> el [http://www.php.net/features.safe-mode modo seguro] de PHP está activado.\nEste modo puede causar problemas, especialmente en la carga de archivos y 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ás usando Mandrake, instala 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-memory-bad": "<strong>Advertencia:</strong> el parámetro <code>memory_limit</code> de PHP es $1.\nProbablemente sea demasiado bajo.\n¡La instalación puede fallar!",
"config-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-no-cache": "<strong>Advertencia:</strong> 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á activado.",
+ "config-mod-security": "<strong>Advertencia:</strong> tu servidor web tiene activado [http://modsecurity.org/ mod_security]/mod_security2. Muchas de sus configuraciones comunes pueden causar problemas a MediaWiki u otro software que permita a los usuarios publicar contenido arbitrario. De ser posible, deberías desactivarlo. Si no, consulta la [http://modsecurity.org/documentation/ documentación de mod_security] o contacta con el administrador de tu servidor si encuentras errores aleatorios.",
"config-diff3-bad": "GNU diff3 no se encuentra.",
"config-git": "Se encontró el software de control de versiones Git: <code>$1</code>.",
"config-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-scaling": "No se ha encontrado la biblioteca GD o ImageMagik.\nSe desactivará 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-uploads-not-safe": "<strong>Advertencia:</strong> tu directorio predeterminado 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": "<strong>Advertencia:</strong> tu directorio predeterminado para cargas (<code>$1</code>) no está comprobado contra la vulnerabilidad\n de ejecución arbitraria de \"scripts\" durante la instalación por línea de comandos.",
"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-help": "Si tu servidor de base de datos está en otro servidor, escribe el nombre del equipo o su dirección IP aquí.\n\nSi estás utilizando alojamiento web compartido, tu proveedor debería darte el nombre correcto del servidor en su documentación.\n\nSi vas a instalar en un servidor Windows y a utilizar MySQL, el uso de \"localhost\" como nombre del servidor puede no funcionar. Si es así, intenta poner \"127.0.0.1\" como dirección IP local.\n\nSi utilizas PostgreSQL, deja este campo vacío 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-help": "Elige un nombre que identifique tu wiki.\nNo debe contener espacios.\n\nSi estás utilizando alojamiento web compartido, tu proveedor te dará un nombre específico de base de datos para que lo utilices, o bien te 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-password-empty": "Escribe 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": "Debes introducir un valor para \"{{int:config-db-username}}\"",
+ "config-db-install-username": "Escribe 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, sino el nombre de usuario para la base de datos.",
+ "config-db-install-password": "Escribe 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, sino la contraseña para la base de datos.",
+ "config-db-install-help": "Escribe el nombre de usuario y la contraseña que se utilizarán para conectarse 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-wiki-help": "Escribe el nombre de usuario y la contraseña que se utilizarán 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-prefix-help": "Si necesitas compartir una base de datos entre múltiples wikis, o entre MediaWiki y otra aplicación web, puedes optar por agregar un prefijo a todos los nombres de tabla para evitar conflictos.\nNo utilices 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",
@@ -142,18 +142,18 @@
"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-db-schema-help": "Este esquema usualmente estará bien.\nCámbialos solo si lo necesitas.",
+ "config-pg-test-error": "No se puede conectar a la base de datos <strong>$1</strong>: $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-sqlite-dir-help": "SQLite almacena todos los datos en un único archivo.\n\nEl directorio que proporciones debe poder escribirse 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\nConsidera poner la base de datos en algún otro sitio, por ejemplo en <code>/var/lib/mediawiki/tuwiki</code> .",
+ "config-oracle-def-ts": "Espacio de tablas predeterminado:",
"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-support-info": "MediaWiki es compatible con los siguientes sistemas de bases de datos:\n\n$1\n\nSi no encuentras en el listado el sistema de base de datos que estás intentando utilizar, sigue las instrucciones enlazadas arriba para activar la compatibilidad.",
"config-dbsupport-mysql": "* [{{int:version-db-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)",
@@ -165,44 +165,44 @@
"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-missing-db-name": "Debes escribir un valor para \"{{int:config-db-nombre}}\".",
+ "config-missing-db-host": "Debes escribir un valor para \"{{int:config-db-host}}\".",
+ "config-missing-db-server-oracle": "Debes escribir 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-db-name": "El nombre de la base de datos \"$1\" no es vá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\" no es válido.\nUsa sólo caracteres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_) y guiones (-).",
+ "config-connection-error": "$1.\n\nVerifica el servidor, el nombre de usuario y la contraseña, e intenta 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-postgres-old": "Se requiere PostgreSQL $1 o posterior. Tienes la versión $2.",
+ "config-mssql-old": "Se requiere Microsoft SQL Server $1 o posterior. Tienes la versión $2.",
+ "config-sqlite-name-help": "Elige el nombre que identificará a tu wiki.\nNo uses espacios o guiones.\nEste nombre se usará 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-dir-unwritable": "No se puede escribir en el directorio \"$1\".\nModifica sus permisos para que el servidor web pueda escribir en él, y vuelve a intentarlo.",
+ "config-sqlite-connection-error": "$1.\n\nVerifica el directorio 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-can-upgrade": "Esta base de datos contiene tablas de MediaWiki.\nPara actualizarlas a MediaWiki $1, haz clic en <strong>Continuar</strong>.",
+ "config-upgrade-done": "Actualización completa.\n\nYa puedes [$1 empezar a usar tu wiki].\n\nSi quieres regenerar tu archivo <code>LocalSettings.php</code>, haz clic en el botón de abajo.\nEsto <strong>no se recomienda</strong> a menos que estés teniendo problemas con tu wiki.",
+ "config-upgrade-done-no-regenerate": "Actualización completa.\n\nYa puedes [$1 empezar a usar tu 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-unknown-collation": "<strong>Advertencia:</strong> 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-help": "Elige el usuario y contraseña que el servidor web usará para conectarse al servidor de la base de datos durante el funcionamiento 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-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í ya 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-myisam-dep": "<strong>Advertencia:</strong> has seleccionado MyISAM como motor de almacenamiento de MySQL, el cual no está recomendado para usarse con MediaWiki, porque:\n* apenas soporta concurrencia 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 tu instalación de MySQL soporta InnoDB, es muy recomendable que lo elijas en su lugar.\nSi tu instalación de MySQL no soporta InnoDB, quizás es el momento de una actualización.",
+ "config-mysql-only-myisam-dep": "<strong>Advertencia:</strong> 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 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\nTu instalación de MySQL no soporta InnoDB, quizás es el momento de una actualización.",
+ "config-mysql-engine-help": "<strong>InnoDB</strong> es casi siempre la mejor opción, dado que soporta bien los accesos simultáneos.\n\n<strong>MyISAM</strong> puede ser más rápido en instalaciones con 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",
@@ -214,33 +214,33 @@
"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-site-name-blank": "Escribe un nombre de sitio.",
+ "config-project-namespace": "Espacio de nombres del 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-ns-invalid": "El espacio de nombres especificado \"<nowiki>$1</nowiki>\" no es válido.\nEspecifica uno diferente.",
+ "config-ns-conflict": "El espacio de nombres especificado \"<nowiki>$1</nowiki>\" entra en conflicto con uno predeterminado de MediaWiki.\nEspecifica uno 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-name-blank": "Escribe un nombre de usuario de administrador.",
+ "config-admin-name-invalid": "El nombre de usuario especificado \"<nowiki>$1</nowiki>\" no es válido.\nEspecifica un nombre de usuario diferente.",
+ "config-admin-password-blank": "Escribe 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-email-help": "Escribe aquí una dirección de correo electrónico para que te permita recibir mensajes de otros usuarios del wiki, restablecer tu contraseña y recibir notificaciones de cambios 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-admin-error-password": "Error interno al establecer una contraseña para el administrador \"<nowiki>$1</nowiki>\": <pre>$2</pre>",
+ "config-admin-error-bademail": "Has escrito una dirección de correo electrónico no válida.",
+ "config-subscribe": "Suscribirse a la [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de correo de anuncios de 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-almost-done": "¡Ya casi has terminado!\nAhora puedes saltarte el resto de los pasos e instalar el wiki ya.",
"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:",
@@ -347,6 +347,6 @@
"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>",
+ "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
index 8fae026e..a3732a80 100644
--- a/includes/installer/i18n/et.json
+++ b/includes/installer/i18n/et.json
@@ -3,10 +3,13 @@
"authors": [
"Avjoska",
"Pikne",
- "Boxmein"
+ "Boxmein",
+ "Cumbril",
+ "Roland"
]
},
"config-information": "Teave",
+ "config-upgrade-key-missing": "Tuvastati olemasolev MediaWiki install.\nSelle installi täiendamiseks lisa palun järgmine rida faili <code>LocalSettings.php</code> lõppu:\n\n$1",
"config-session-error": "Tõrge seansi alustamisel: $1",
"config-your-language": "Oma keel:",
"config-wiki-language": "Viki keel:",
@@ -23,12 +26,17 @@
"config-page-complete": "Valmis!",
"config-page-restart": "Alusta installimist uuesti",
"config-page-readme": "Loe mind",
+ "config-page-releasenotes": "Redaktsioonimärkmed",
"config-page-copying": "Kopeerimine",
"config-page-upgradedoc": "Uuendamine",
"config-page-existingwiki": "Olemasolev viki",
"config-restart": "Jah, tee taaskäivitus",
+ "config-env-php": "PHP $1 on paigaldatud.",
"config-env-hhvm": "HHVM $1 on installitud.",
+ "config-diff3-bad": "GNU diff3 ei leitud.",
+ "config-db-type": "Andmebaasi tüüp:",
"config-db-name": "Andmebaasi nimi:",
+ "config-db-name-oracle": "Andmebaasi skeem:",
"config-db-username": "Andmebaasi kasutajanimi:",
"config-db-password": "Andmebaasi parool:",
"config-db-port": "Andmebaasi port:",
@@ -38,7 +46,7 @@
"config-project-namespace": "Projekti nimeruum:",
"config-ns-generic": "Projekt",
"config-admin-box": "Administraatorikonto",
- "config-admin-name": "Sinu nimi:",
+ "config-admin-name": "Sinu kasutajanimi:",
"config-admin-password": "Parool:",
"config-admin-password-confirm": "Parool uuesti:",
"config-admin-name-blank": "Sisesta administraatori kasutajanimi.",
@@ -47,7 +55,11 @@
"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-wiki": "Avalik viki",
+ "config-profile-no-anon": "Registreerumine nõutav",
+ "config-profile-fishbowl": "Ainult volitatud kasutajad",
"config-profile-private": "Eraviki",
+ "config-profile-help": "Vikid toimivad kõige paremini siis, kui lased neid redigeerida nii paljudel inimestel kui võimalik.\nMediaWikis on lihtne viimaseid muudatusi üle vaadata ja pöörata tagasi oskamatute või pahatahtlike kasutajate tehtud kahju.\n\nEnt inimesed on leidnud MediaWikile mitmesuguseid erinevaid kasutusvõimalusi ja mõnikord pole lihtne kõiki veenda viki meetodi kasulikkuses. \nSeega on sul valik.\n\n\"<strong>{{int:config-profile-wiki}}</strong>\" annab kõigile redigeerimisvõimaluse isegi sisse logimata.\nMudeli \"<strong>{{int:config-profile-no-anon}}</strong>\" viki tagab lisavastutuse, kuid võib juhuslikud kaastöölised eemale peletada.\n\nStsenaarium \"<strong>{{int:config-profile-fishbowl}}</strong>\" võimaldab redigeerida heaks kiidetud kasutajatel, kuid avalikkus saab lehekülgi ja nende ajalugu vaadata.\n\"<strong>{{int:config-profile-private}}</strong>\" laseb vaid heaks kiidetud kasutajatel lehekülgi vaadata ja samadel kasutajatel on õigus lehekülgi redigeerida.\n\nPärast paigaldamist on kasutajaõigusi võimalik täpsemalt häälestada, vaata [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights kasutusjuhendi vastavat kohta].",
"config-license": "Autoriõigus ja litsents:",
"config-license-none": "Litsentsijaluseta",
"config-license-cc-by-sa": "Creative Commonsi litsents \"Autorile viitamine + jagamine samadel tingimustel\"",
diff --git a/includes/installer/i18n/eu.json b/includes/installer/i18n/eu.json
index 4b32743c..baab24aa 100644
--- a/includes/installer/i18n/eu.json
+++ b/includes/installer/i18n/eu.json
@@ -30,6 +30,7 @@
"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-env-hhvm": "HHVM $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",
@@ -41,10 +42,11 @@
"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-mysql": "MySQL (edo bateragarria)",
"config-type-postgres": "PostgreSQL",
"config-type-sqlite": "SQLite",
"config-type-oracle": "Oracle",
+ "config-type-mssql": "Microsoft SQL Server",
"config-header-mysql": "MySQL hobespenak",
"config-header-postgres": "PostgreSQL hobespenak",
"config-header-sqlite": "SQLite hobespenak",
@@ -67,6 +69,7 @@
"config-profile-wiki": "Wikia ireki",
"config-profile-private": "Wiki pribatua",
"config-license": "Copyright eta lizentzia:",
+ "config-license-cc-0": "Creative Commons Zero (Jabari Publikoa)",
"config-license-pd": "Domeinu Askea",
"config-email-settings": "E-posta hobespenak",
"config-logo": "Logo URL:",
diff --git a/includes/installer/i18n/fa.json b/includes/installer/i18n/fa.json
index 02ffd0d1..6452fc6e 100644
--- a/includes/installer/i18n/fa.json
+++ b/includes/installer/i18n/fa.json
@@ -7,7 +7,8 @@
"Omidh",
"Pouyana",
"Reza1615",
- "Alirezaaa"
+ "Alirezaaa",
+ "Danialbehzadi"
]
},
"config-desc": "نصب کنندهٔ ویکی‌مدیا",
@@ -47,17 +48,16 @@
"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-copyright": "===حق چاپ و شرایط===\n$1\nاین برنامه، یک نرم‌افزاری آزاد است. شما می‌توانید آن را بازتوزیع کرده و/یا با شرایط نگارش ۲ یا (با نظر خودتان) هر نگارش جدیدتری از پروانه جامع همگانی گنو که توسط بنیاد نرم‌افزار آزاد منتشر شده، تغییر دهید.\n\nاین برنامه با امید این که مفید واقع‌ شود توزیع شده‌است،اما '''بدون هیچ ضمانتی'''; حتی بدون اشارهٔ ضمانتی از '''قابلیت عرضه''' یا ''' صلاحیت برای یک هدف خاص'''.\nبرای جزئیات بیش‌تر پروانه جامع همگانی گنو را مشاهده کنید.\n\nشما باید <doclink href=Copying> یک نگارش ازمجوز عمومی کلی </doclink> همراه این برنامه دریافت کرده باشید. در غیر این صورت با بنیاد نرم‌افزار آزاد، ایالات متحده امریکا، بوستون، خیابان فرانکلین، پلاک ۵۱، طبقه پنجم، صندوق پستی MA۰۲۱۱۰-۱۳۰ مکاتبه کنید، یا [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": "پی‌اچ‌پی $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-no-db": "درایور پایگاه اطلاعاتی مناسب پیدا نشد! شما لازم دارید یک درایور پایگاه اطلاعاتی برای پی‌اچ‌پی نصب کنید.انواع پایگاه اطلاعاتی زیر پشتیبانی شده‌اند:$1.\nاگر شما در گروه اشتراک‌گذاری هستید، از تهیه کنندهٔ گروه خود برای نصب یک درایور پایگاه اطلاعاتی مناسب {{PLURAL:$2|سوأل کنید.|سوأل کنید.}}\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] را برای کمک در مورد نحوه انجام این کار ببینید.",
@@ -102,7 +102,7 @@
"config-db-name": "نام پایگاه اطلاعاتی:",
"config-db-name-help": "نامی را انتخاب کنید که ویکی شما را شناسایی کند.\nنباید شامل فاصله باشد.\nاگر از گروه شبکهٔ اشتراک‌گذاری استفاده می‌کنید، تهیه‌کنندهٔ گروهتان یا باید به شما نام یک پایگاه اطلاعاتی مشخص برای استفاده بدهد یا برای ایجاد پایگاه‌های اطلاعاتی از طریق یک کنترل پنل به شما اجازه بدهد.",
"config-db-name-oracle": "طرح کلی پایگاه اطلاعاتی:",
- "config-db-account-oracle-warn": "برای نصب برنامهٔ اوراکل به‌عنوان پایگاه اطلاعاتی در بخش گذشته،سه سناریو پشتیبانی شده است:\nاگر مایل به ایجاد حساب پایگاه اطلاعاتی به عنوان بخشی از روند نصب هستید، لطفاً یک حساب با نقش اس‌وای‌اس‌دی‌بی‌ای به عنوان حساب پایگاه اطلاعاتی برای نصب تهیه کنید و اعتبارنامه‌های مطلوبی را برای حساب دردسترس شبکه تعیین کنید، به عبارتی دیگر یا می‌توانید حساب دردسترس شبکه را به طور دستی ایجاد کنید و تنها آن حساب را تهیه کنید (اگر مستلزم مجوزهایی برای ایجاد موضوعات طرح کلی باشد) یا دو حساب دیگر تهیه کنید،یکی با ایجاد مزایا و یک حساب محدود برای دسترسی شبکه.\nمتنی برای ایجاد یک حساب با مزایای لازم بنویسید که می‌تواند در فهرست\"نگهداری/برنامهٔ اوراکل\" این نصب یافت شود. به یاد داشته باشید که استفاده از یک حساب محدود،همهٔ قابلیت‌های نگهداری با حساب پیش‌فرض را غیرفعال خواهد کرد.",
+ "config-db-account-oracle-warn": "برای نصب برنامهٔ اوراکل به عنوان پایگاه اطلاعاتی در بخش گذشته،سه سناریو پشتیبانی شده است:\nاگر مایل به ایجاد حساب پایگاه اطلاعاتی به عنوان بخشی از روند نصب هستید، لطفاً یک حساب با نقش اس‌وای‌اس‌دی‌بی‌ای به عنوان حساب پایگاه اطلاعاتی برای نصب تهیه کنید و اعتبارنامه‌های مطلوبی را برای حساب دردسترس شبکه تعیین کنید، به عبارتی دیگر یا می‌توانید حساب دردسترس شبکه را به طور دستی ایجاد کنید و تنها آن حساب را تهیه کنید (اگر مستلزم مجوزهایی برای ایجاد موضوعات طرح کلی باشد) یا دو حساب دیگر تهیه کنید،یکی با ایجاد مزایا و یک حساب محدود برای دسترسی شبکه.\nمتنی برای ایجاد یک حساب با مزایای لازم بنویسید که می‌تواند در فهرست\"نگهداری/برنامهٔ اوراکل\" این نصب یافت شود. به یاد داشته باشید که استفاده از یک حساب محدود،همهٔ قابلیت‌های نگهداری با حساب پیش‌فرض را غیرفعال خواهد کرد.",
"config-db-install-account": "حساب کاربری برای نصب",
"config-db-username": "نام کاربری پایگاه اطلاعات:",
"config-db-password": "گذرواژه پایگاه داده‌ها:",
@@ -127,7 +127,7 @@
"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-sqlite-dir-help": "اس‌کیولایت همهٔ اطلاعات را در یک پوشهٔ جداگانه ذخیره می‌کند.\nفهرستی را که به وجود‌ آوردید باید در طی نصب به‌ وسیلهٔ وب‌سرور قابل نوشتن باشد.\n<strong>نباید</strong> از طریق وب در دسترس باشد، به همین دلیل ما آن را در جایی که پوشه‌های پی‌اچ‌پی شما هست، قرار نمی‌دهیم.\nنصب کننده یک پوشهٔ <code>.htaccess</code> همراه آن خواهدآورد،اما اگر این کار را انجام ندهد،کسی می‌تواند به پایگاه اطلاعاتی شما دسترسی پیدا کند.\nاطلاعات خام کاربر شامل (آدرس‌های ایمیل، علامت‌‌ها با شماره‌های رمز عبور) به خوبی پاک کردن تغییرات و دیگر اطلاعات محرمانه در ویکی.\nقرار دادن پایگاه اطلاعاتی باهم را در جایی دیگر در نظر بگیرید، برای مثال در <code>/var/lib/mediawiki/yourwiki</code>.",
"config-oracle-def-ts": "جدول پیش فرض:",
"config-oracle-temp-ts": "جدول موقت:",
"config-type-mysql": "مای‌اس‌کیو‌ال (یا سازگار)",
@@ -211,21 +211,21 @@
"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-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-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-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-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": "حق تکثیر و مجوز:",
@@ -238,19 +238,19 @@
"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-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-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> در پوشه ریشه مدیاویکی را تغییر دهید که کارسازهای وب قادر به نوشتن بر روی آن باشند. سپس این قابلیت را فعال کنید.",
diff --git a/includes/installer/i18n/fi.json b/includes/installer/i18n/fi.json
index c40544df..6b01b61f 100644
--- a/includes/installer/i18n/fi.json
+++ b/includes/installer/i18n/fi.json
@@ -13,7 +13,10 @@
"Elseweyr",
"Lliehu",
"Syreeni",
- "Stryn"
+ "Stryn",
+ "SMAUG",
+ "SuperPete",
+ "McSalama"
]
},
"config-desc": "MediaWiki-asennin",
@@ -68,17 +71,32 @@
"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-git": "Löydetty Git versionhallintaohjelmisto: <code>$1</code>",
+ "config-git-bad": "Git versionhallintaohjelmistoa ei löydy.",
"config-imagemagick": "Löydettiin ImageMagick: <code>$1</code>.\nKuvien esikatselukuvat otetaan samalla käyttöön jos otetaan tiedostojen tallennus.",
+ "config-gd": "Löydettiin sisäänrakennettu GD-grafiikkakirjasto.\nKuvista luodaan esikatseluversiot automaattisesti, jos otat käyttöön tiedostojen lähettämisen.",
+ "config-no-scaling": "GD-kirjastoa tai ImageMagick-ohjelmaa ei löydy. \nKuvista ei luoda esikatseluversioita.",
+ "config-no-uri": "Virhe: Tämänhetkistä URIa ei tunnisteta. Asennus keskeytetään.",
+ "config-no-cli-uri": "<strong>Varoitus:</strong> <code>--scriptpath</code> määrittämättä, käytetään oletusta: <code>$1</code>",
"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-uploads-not-safe": "<strong>Varoitus:</strong> Tiedostojen lähetyshakemistoa <code>$1</code> ei ole suojattu haitalliselta koodilta. MediaWiki tarkistaa kaikki lähetetyt tiedostot, mutta suosittelemme toimimaan ohjeen [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security close this security vulnerability] mukaan ennen kuin tiedostojen lähetys otetaan käyttöön.",
+ "config-no-cli-uploads-check": "<strong>Varoitus:</strong> Tiedostojen lähetyshakemistoa (<code>$1</code>) ei ole tarkistettu haavoittuvuuksien varalta komentoriviasennuksen aikana.",
+ "config-brokenlibxml": "Järjestelmässäsi on käytössä PHP:n ja libxml2:n versioyhdistelmä, joka ei toimi kunnolla ja voi aiheuttaa tiedon vahingoittumista MediaWikissä ja muissa web-sovelluksissa.\nPäivitä libxml2 versioon 2.7.3 tai uudempaan ([https://bugs.php.net/bug.php?id=45996 bug filed with PHP]).\nAsennus keskeytetty.",
+ "config-suhosin-max-value-length": "Suhosin on asennettu ja se rajoittaa GET-parametrin <code>length</code> $1 tavuun.\nMediaWikin ResourceLoader-komponentti pystyy toimimaan tämän kanssa, mutta ohjelmiston suorituskyky heikkenee.\nMikäli mahdollista, aseta muuttuja <code>suhosin.get.max_value_length</code> arvoon 1024 (tai suurempaan) tiedostossa <code>php.ini</code> ja aseta myös <code>$wgResourceLoaderMaxQueryLength</code> samaksi arvoksi tiedostossa <code>LocalSettings.php</code>.",
"config-db-type": "Tietokannan tyyppi",
"config-db-host": "Tietokantapalvelin",
+ "config-db-host-help": "Jos tietokantapalvelimesi sijaitsee eri palvelimella, syötä palvelimen nimi tai ip-osoite tähän.\n\nJos käytössäsi on ulkoinen palveluntarjoaja, pitäisi palvelimen nimen löytyä yrityksen ohjesivuilta.\n\nJos asennat MediaWikiä Windows-palvelimelle ja käytät MySQL:ää ei palvelimen nimi \"localhost\" välttämättä toimi. Tässä tapauksessa koita käyttää osoitetta 127.0.0.1.\n\nJos käytät PostgreSQL:ää jätä tämä kenttä tyhjäksi.",
+ "config-db-host-oracle": "Tietokannan TNS:",
"config-db-wiki-settings": "Identifioi tämä wiki",
"config-db-name": "Tietokannan nimi",
+ "config-db-name-help": "Valitse wikiäsi kuvaava nimi.\nNimessä ei saa olla välilyöntejä.\n\nMikäli et pysty itse hallitsemaan tietokantojasi, pyydä palveluntarjoajaasi luomaan tietokanta tai tee se palveluntarjoajasi hallintapaneelissa.",
+ "config-db-name-oracle": "Tietokannan rakenne:",
"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-username-empty": "Syötä arvo tiedolle \"{{int:config-db-username}}\".",
"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.",
@@ -93,11 +111,18 @@
"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-db-schema": "MediaWikin rakenne:",
+ "config-db-schema-help": "Tämä rakenne on normaalisti toimiva.\nMuuta rakennetta vain, mikäli on pakko ja tiedät, mitä teet.",
"config-pg-test-error": "Tietokantaan <strong>$1 ei voida muodostaa yhteyttä</strong>: $2",
+ "config-sqlite-dir": "SQLiten datahakemisto:",
+ "config-sqlite-dir-help": "SQLite tallentaa kaiken sisällön yhteen tiedostoon.\n\nPalvelimen pitää pystyä kirjoittamaan tietoa hakemistoon asennuksen aikana.\n\nHakemiston <strong>ei</strong> tulisi olla nähtävissä www-selaimella. Siksi hakemisto on eri kuin missä PHP-tiedostot sijaitsevat.\n\nAsennusohjelma luo <code>.htaccess</code>-tiedoston, mutta jos sen luomisessa ilmenee ongelmia joku voi päästä käsiksi tietokantaasi. \nTietokannassa on kaikki sähköpostiosoitteet, salasanat, poistetut versiot ja kaikki muu tieto, joka ei näy wikissä.\n\nSuosittelemme tallentamaan tietokannan eri hakemistoon, esimerkiksi <code>/var/lib/mediawiki/yourwiki</code>.",
+ "config-oracle-def-ts": "Oletus taulukkotila:",
+ "config-oracle-temp-ts": "Väliaikainen taulukkotila:",
"config-type-mysql": "MySQL (tai yhteensopiva)",
"config-type-postgres": "PostgreSQL",
"config-type-sqlite": "SQLite",
"config-type-oracle": "Oracle",
+ "config-type-mssql": "Microsoft SQL Server",
"config-header-mysql": "MySQL-asetukset",
"config-header-postgres": "PostgreSQL-asetukset",
"config-header-sqlite": "SQLite-asetukset",
@@ -165,7 +190,13 @@
"config-profile-fishbowl": "Vain hyväksytyt muokkaajat",
"config-profile-private": "Yksityinen wiki",
"config-license": "Tekijänoikeus ja lisenssi:",
+ "config-license-cc-by-sa": "Creative Commons Nimeä-Tarttuva",
+ "config-license-cc-by": "Creative Commons Nimeä",
+ "config-license-cc-by-nc-sa": "Creative Commons Nimeä-Epäkaupallinen-Tarttuva",
+ "config-license-cc-0": "Creative Commons Zero (Public Domain)",
+ "config-license-gfdl": "GNU Free Documentation -lisenssi 1.3 tai uudempi",
"config-license-pd": "Public domain",
+ "config-license-cc-choose": "Valitse mukautettu Creative Commons -lisenssi",
"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ä.",
@@ -187,14 +218,25 @@
"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-help": "Seuraavat teemat löydettiin hakemistosta <code>./skins</code>. Ota käyttöön vähintään yksi teema ja aseta se oletukseksi.",
+ "config-skins-use-as-default": "Käytä tätä teemaa oletuksena.",
+ "config-skins-missing": "Teemoja ei löytynyt; MediaWiki käyttää väliaikaista teemaa, kunnes asennat toimivia.",
"config-skins-must-enable-some": "Sinut täytyy valita ainakin yksi ulkoasu.",
+ "config-skins-must-enable-default": "Oletusteeman pitää olla käytössä.",
"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-install-schema": "Luodaan rakennetta",
+ "config-install-pg-schema-not-exist": "PostgreSQL-rakennetta ei ole olemassa.",
+ "config-install-pg-schema-failed": "Taulun luominen epäonnistui.\nVarmista, että käyttäjätunnus \"$1\" pystyy kirjoittamaan rakenteeseen \"$2\".",
+ "config-install-pg-commit": "Muutoksia tallennetaan",
+ "config-install-pg-plpgsql": "Tarkistetaan PL/pgSQL:n kieltä.",
+ "config-pg-no-plpgsql": "PL/pgSQL-kieli pitää asentaa tietokantaan $1",
"config-pg-no-create-privs": "Määrittelemälläsi tilillä ei ole riittävästi oikeuksia luoda tiliä.",
+ "config-pg-not-in-role": "Määrittelemäsi web-käyttäjän tili on jo olemassa.\nMäärittelemälläsi käyttäjätilillä ei ole pääkäyttäjäoikeuksia eikä se toimi web-käyttäjän roolissa. Käyttäjätili ei pysty luomaan tarvittavia objekteja.\n\nMediaWiki vaatii, että web-käyttäjän pitää pystyä hallitsemaan tauluja. Anna toinen web-käyttäjätunnus tai klikkaa \"takaisin\" ja määrittele käyttäjätunnus, joka toimii asennuksessa.",
"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",
@@ -204,15 +246,22 @@
"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": "Luodaan oletustaulua interwikille",
"config-install-interwiki-list": "Tiedostoa <code>interwiki.list</code> ei voitu lukea.",
+ "config-install-interwiki-exists": "<strong>Varoitus:</strong> interwiki-taulussa on jo tietueita, ohitetaan oletuslista.",
+ "config-install-stats": "Alustetaan tilastoja",
"config-install-keys": "Muodostetaan salausavaimia",
+ "config-install-updates": "Estä tarpeettomien päivitysten asennus",
"config-install-sysop": "Luodaan ylläpitäjän tiliä",
"config-install-subscribe-fail": "Liittyminen mediawiki-announce listalle epäonnistui: $1",
+ "config-install-subscribe-notpossible": "cURL-ohjelmaa ei ole asennettu eikä <code>allow_url_fopen</code> ole saatavilla.",
"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-install-done": "<strong>Onnittelut!</strong>\nMediaWiki on asennettu onnistuneesti\n\nAsennusohjelma on luonut <code>LocalSettings.php</code> -tiedoston.\nSiinä on kaikki MediaWikin asetukset.\n\nLataa tiedosto ja laita se MediaWikin asennushakemistoon (sama kuin missä on index.php). Lataamisen olisi pitänyt alkaa automaattisesti.\n\nMikäli keskeytit latauksen, käynnistä se uudestaan tästä linkistä:\n\n$3\n\n<strong>HUOM!</strong> Mikäli et nyt lataa tiedostoa, joudut aloittamaan asennuksen alusta.\n\nKun olet laittanut tiedoston oikeaan paikkaan voit <strong>[$2 mennä wikiisi]</strong>.",
"config-download-localsettings": "Lataa <code>LocalSettings.php</code>",
"config-help": "ohje",
+ "config-help-tooltip": "Klikkaa laajentaaksesi",
"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.'''",
diff --git a/includes/installer/i18n/fr.json b/includes/installer/i18n/fr.json
index 9fd6726e..c9750903 100644
--- a/includes/installer/i18n/fr.json
+++ b/includes/installer/i18n/fr.json
@@ -21,7 +21,8 @@
"Maxim21",
"Wladek92",
"Scoopfinder",
- "Seb35"
+ "Seb35",
+ "Linedwell"
]
},
"config-desc": "Le programme d’installation de MediaWiki",
@@ -71,7 +72,7 @@
"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-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. {{PLURAL:$2|Le type suivant|Les types suivants}} de bases de données {{PLURAL:$2|est reconnu|sont reconnus}} : $1.\n\nSi vous avez compilé PHP vous-même, reconfigurez-le avec un client de base de données actif, 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.",
diff --git a/includes/installer/i18n/frc.json b/includes/installer/i18n/frc.json
index e05cd252..ac60dea7 100644
--- a/includes/installer/i18n/frc.json
+++ b/includes/installer/i18n/frc.json
@@ -1,5 +1,24 @@
{
- "@metadata": [],
+ "@metadata": {
+ "authors": [
+ "Hangmanwa7id"
+ ]
+ },
+ "config-your-language": "Ton langue:",
+ "config-page-name": "Nom",
+ "config-page-options": "Options",
+ "config-page-install": "Installer",
+ "config-page-complete": "Terminé!",
+ "config-sqlite-name-help": "Choisir un nom qui identifie ton wiki.\nFait user pas ni d'espaces ni des traits d'union\nIl va user pour fichier de données SQLite.",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-binary": "Binaire",
+ "config-mysql-utf8": "UTF-8",
+ "config-ns-generic": "Projet",
+ "config-ns-other-default": "MonWiki",
+ "config-admin-name": "Ton nom d'useur:",
+ "config-admin-password": "Mot de passe:",
+ "config-admin-email": "Adresse d'email:",
"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/fy.json b/includes/installer/i18n/fy.json
index 1eb0d894..39c46b53 100644
--- a/includes/installer/i18n/fy.json
+++ b/includes/installer/i18n/fy.json
@@ -1,9 +1,19 @@
{
"@metadata": {
"authors": [
- "Seb35"
+ "Seb35",
+ "Robin0van0der0vliet"
]
},
+ "config-information": "Ynformaasje",
+ "config-back": "← Foarige",
+ "config-page-language": "Taal",
+ "config-page-name": "Namme",
+ "config-page-options": "Opsjes",
+ "config-mysql-binary": "Binêr",
+ "config-ns-generic": "Projekt",
+ "config-admin-password": "Wachtwurd:",
+ "config-help": "help",
"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]"
+ "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 ynstellingen]\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/gl.json b/includes/installer/i18n/gl.json
index a42cb179..940b4c01 100644
--- a/includes/installer/i18n/gl.json
+++ b/includes/installer/i18n/gl.json
@@ -54,7 +54,7 @@
"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-no-db": "Non se puido atopar un controlador axeitado para a base de datos! Necesita instalar un controlador de base de datos para PHP.\n{{PLURAL:$2|Acéptase o seguinte tipo|Acéptanse os seguintes tipos}} de base de datos: $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.",
diff --git a/includes/installer/i18n/gor.json b/includes/installer/i18n/gor.json
new file mode 100644
index 00000000..b080848b
--- /dev/null
+++ b/includes/installer/i18n/gor.json
@@ -0,0 +1,48 @@
+{
+ "@metadata": {
+ "authors": [
+ "Marwan Mohamad"
+ ]
+ },
+ "config-information": "habari",
+ "config-wiki-language": "bahasa wiki",
+ "config-back": "Mohalingo",
+ "config-continue": "Turusi",
+ "config-page-language": "Bahasa",
+ "config-page-dbconnect": "mohumbuto ode database",
+ "config-page-upgrade": "Popobohuwa umapilopohuli",
+ "config-page-dbsettings": "Aturu lo database",
+ "config-page-name": "Tanggulo",
+ "config-page-options": "Tulawotolo",
+ "config-page-install": "Mopohuli",
+ "config-page-complete": "Yilapato",
+ "config-page-restart": "Ulangiya instalasi",
+ "config-page-readme": "Pobacawa Wau",
+ "config-page-releasenotes": "Tuladu mopolopato",
+ "config-page-copying": "Mohemi",
+ "config-page-upgradedoc": "Mopobohu",
+ "config-page-existingwiki": "Wiki uwoluwo",
+ "config-help-restart": "Yio mohuto moluluta ngaamila data utahu-tahu ma pilopomaso wawu mopobohu upasi-pasi",
+ "config-restart": "Jo, potumula ulangi",
+ "config-env-good": "Yio mowali mopopasi MediaWiki",
+ "config-env-bad": "Yio dilamowali mopopasi MediaWiki",
+ "config-env-php": "PHP$1 mayilepasi",
+ "config-env-hhvm": "HHVM $1 mayilepasi",
+ "config-apc": "[http://www.php.net/apc APC] mayilepasi",
+ "config-diff3-bad": "GNU diff3 ja yilotapu",
+ "config-using-server": "Momake tanggulo server \"<nowiki>$1<nowiki>\"",
+ "config-using-uri": "Momake server URL \"<nowiki>$1$2</nowiki>\"",
+ "config-db-host": "Tiyombu lo basis data",
+ "config-db-host-oracle": "Basis data TNS",
+ "config-db-wiki-settings": "Lolohe wiki botiye",
+ "config-db-name": "Tanggulo basis data",
+ "config-db-name-oracle": "Skema lo basis data",
+ "config-db-username": "Basis data lo tanggulo user",
+ "config-charset-mysql5": "MySQL 4.1/5.0 UFT-8",
+ "config-type-mysql": "MySQL (meyalo umopasiya)",
+ "config-header-mysql": "Aturangi lo MySQL",
+ "config-header-postgres": "Aturangi lo PostgreSQL",
+ "config-header-sqlite": "Aturangi lo SQLite",
+ "config-header-oracle": "Aturangi lo Oracle",
+ "config-header-mssql": "Aturangi lo Server Microsoft SQL"
+}
diff --git a/includes/installer/i18n/he.json b/includes/installer/i18n/he.json
index dd14e633..34190891 100644
--- a/includes/installer/i18n/he.json
+++ b/includes/installer/i18n/he.json
@@ -56,7 +56,7 @@
"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-no-db": "לא נמצא דרייבר מסד נתונים מתאים. יש להתקין דרייבר מסד נתונים ל־PHP.\n{{PLURAL:$2|נתמך הסוג הבא של מסד נתונים|נתמכים הסוגים הבאים של מסדי נתונים}}: $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] להסבר איך לעשות את זה.",
diff --git a/includes/installer/i18n/hi.json b/includes/installer/i18n/hi.json
index b3779e4f..629b53b5 100644
--- a/includes/installer/i18n/hi.json
+++ b/includes/installer/i18n/hi.json
@@ -2,10 +2,17 @@
"@metadata": {
"authors": [
"Smtchahal",
- "Vivek Rai"
+ "Vivek Rai",
+ "Phoenix303",
+ "संजीव कुमार"
]
},
+ "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-your-language": "आपकी भाषा:",
"config-wiki-language": "विकी भाषा:",
@@ -14,16 +21,21 @@
"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-env-php-toolow": "PHP $1 स्थापित किया गया है।\nतथापि, मीडियाविकि PHP $2 या उच्चतर की आवश्यकता है।",
+ "config-db-wiki-settings": "इस विकि को पहचानें",
"config-mssql-auth": "प्रमाणन प्रकार:",
"config-mssql-sqlauth": "SQL सर्वर प्रमाणन",
"config-site-name": "विकि का नाम:",
diff --git a/includes/installer/i18n/hu.json b/includes/installer/i18n/hu.json
index a150fbcd..cf5ad454 100644
--- a/includes/installer/i18n/hu.json
+++ b/includes/installer/i18n/hu.json
@@ -6,7 +6,8 @@
"아라",
"Dj",
"Misibacsi",
- "Tacsipacsi"
+ "Tacsipacsi",
+ "Dorgan"
]
},
"config-desc": "A MediaWiki telepítője",
@@ -51,14 +52,12 @@
"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-env-hhvm": "HHVM verziója: $1",
"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-db": "Nem sikerült egyetlen használható adatbázis-illesztőprogramot sem találni. Telepítened kell egyet a PHP-hez.\nA következő {{PLURAL:$2|adatbázistípus támogatott|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 például a php5-mysql csomagra 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.",
@@ -221,7 +220,7 @@
"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-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 azt szeretnéd, hogy a Wikipédián felhasználhassák a wikidben található szöveget, akkor a <strong>{{int:config-license-cc-by-sa}}</strong> 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.",
@@ -261,6 +260,8 @@
"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-skins": "Felületek",
+ "config-skins-use-as-default": "Felület használata alapértelmezettként",
"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",
diff --git a/includes/installer/i18n/ia.json b/includes/installer/i18n/ia.json
index 62afb44f..0e8f5973 100644
--- a/includes/installer/i18n/ia.json
+++ b/includes/installer/i18n/ia.json
@@ -47,6 +47,7 @@
"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-env-hhvm": "HHVM $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].",
@@ -55,6 +56,7 @@
"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-gpc": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] es active!</strong>\nIste option corrumpe le datos entrate de maniera imprevisibile.\nTu non pote installar o usar MediaWiki si iste option non es disactivate.",
"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.",
@@ -65,6 +67,7 @@
"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-iconv": "<strong>Fatal:</strong> PHP debe esser compilate con supporto pro le [http://www.php.net/manual/en/iconv.installation.php extension iconv].",
"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",
@@ -232,7 +235,7 @@
"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-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 <strong>{{int:config-license-cc-by-sa}}</strong>.\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.",
@@ -272,6 +275,12 @@
"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-skins": "Apparentias",
+ "config-skins-help": "Hic supra es le lista de apparentias detegite in tu directorio <code>./skins</code>. Tu debe activar al minus un de illos e seliger le predefinite.",
+ "config-skins-use-as-default": "Usar iste apparentia como predefinite",
+ "config-skins-missing": "Nulle apparentia ha essite trovate; MediaWiki usara un apparentia de reserva usque tu installa alcun apparentia complete.",
+ "config-skins-must-enable-some": "Tu debe seliger al minus un apparentia a activar.",
+ "config-skins-must-enable-default": "Le apparentia seligite como predefinite debe esser activate.",
"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",
@@ -301,6 +310,8 @@
"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-updates": "Impedir le execution de actualisationes innecessari",
+ "config-install-updates-failed": "<strong>Error:</strong> Le insertion de claves de actualisation in le tabellas ha fallite con le error sequente: $1",
"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.",
diff --git a/includes/installer/i18n/id.json b/includes/installer/i18n/id.json
index c2411ef0..e85e735a 100644
--- a/includes/installer/i18n/id.json
+++ b/includes/installer/i18n/id.json
@@ -8,7 +8,8 @@
"아라",
"C5st4wr6ch",
"Seb35",
- "Arifin.wijaya"
+ "Arifin.wijaya",
+ "Ilham151096"
]
},
"config-desc": "Penginstal untuk MediaWiki",
@@ -48,11 +49,12 @@
"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-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 <strong>tanpa jaminan apa pun</strong>; bahkan tanpa jaminan tersirat untuk <strong>dapat diperjualbelikan</strong> atau <strong>sesuai untuk tujuan tertentu</strong>.\nLihat GNU General Public License untuk lebih jelasnya.\n\nAnda seharusnya telah menerima <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-env-hhvm": "HHVM $1 telah dipasang.",
"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].",
diff --git a/includes/installer/i18n/it.json b/includes/installer/i18n/it.json
index a61fff29..b9aab3ac 100644
--- a/includes/installer/i18n/it.json
+++ b/includes/installer/i18n/it.json
@@ -10,7 +10,8 @@
"Lucas2",
"Ontsed",
"Seb35",
- "Nemo bis"
+ "Nemo bis",
+ "Ricordisamoa"
]
},
"config-desc": "Il programma di installazione per MediaWiki",
@@ -60,7 +61,7 @@
"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-no-db": "Impossibile trovare un driver adatto per il database! È necessario installare un driver per PHP.\n{{PLURAL:$2|Il seguente formato di database è supportato|I 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.",
@@ -220,7 +221,7 @@
"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-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 propria 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.",
diff --git a/includes/installer/i18n/ja.json b/includes/installer/i18n/ja.json
index 21fcb9d7..dedc3c20 100644
--- a/includes/installer/i18n/ja.json
+++ b/includes/installer/i18n/ja.json
@@ -321,6 +321,7 @@
"config-install-stats": "統計情報の初期化",
"config-install-keys": "秘密鍵の生成",
"config-insecure-keys": "<strong>警告:</strong> インストール中に生成されたセキュアキー ($1) は完璧に安全ではありません。手動で変更することを検討してください。",
+ "config-install-updates": "不要な更新を実行するのを防ぐ",
"config-install-sysop": "管理者のアカウントの作成",
"config-install-subscribe-fail": "mediawiki-announce を購読できませんでした: $1",
"config-install-subscribe-notpossible": "cURL がインストールされていないため、<code>allow_url_fopen</code> を利用できません。",
diff --git a/includes/installer/i18n/ko.json b/includes/installer/i18n/ko.json
index bc828a54..10ca898e 100644
--- a/includes/installer/i18n/ko.json
+++ b/includes/installer/i18n/ko.json
@@ -5,17 +5,18 @@
"아라",
"Hym411",
"Priviet",
- "Namoroka"
+ "Namoroka",
+ "Revi"
]
},
- "config-desc": "미디어위키 설치 프로그램",
+ "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-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-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",
@@ -45,29 +46,32 @@
"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-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-env-php": "PHP $1이(가) 설치되어 있습니다.",
+ "config-env-hhvm": "HHMV $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-unicode-pure-php-warning": "<strong>경고</strong>: 유니코드 정규화를 처리할 [http://pecl.php.net/intl intl PECL 확장 기능]을 사용할 수 없기 때문에 느린 pure-PHP 구현을 대신 사용합니다.\n트래픽이 높은 사이트에서 실행하시려면 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 유니코드 정규화]를 읽어보셔야 합니다.",
+ "config-unicode-update-warning": "<strong>경고</strong>: 유니코드 정규화 래퍼의 설치된 버전은 [http://site.icu-project.org/ ICU 프로젝트]의 라이브러리의 이전 버전을 사용합니다.\n만약 유니코드를 사용하는 것에 대해 우려가 된다면 [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations 업그레이드]해야합니다.",
+ "config-no-db": "적절한 데이터베이스 드라이버를 찾을 수 없습니다! PHP용 데이터베이스 드라이버를 설치해야 합니다.\n다음 데이터베이스 {{PLURAL:$2|유형을 지원합니다}}: $1.\n\nPHP를 직접 컴파일했다면, 예를 들어 <code>./configure --with-mysql</code>을 사용하여, 데이터베이스 클라이언트를 활성화하도록 다시 설정하세요.\n데비안이나 우분투 패키지에서 PHP를 설치했다면 <code>php5-mysql</code> 모듈도 설치해야 합니다.",
+ "config-outdated-sqlite": "<strong>경고</strong>: 최소인 $2 버전보다 낮은 SQLite $1(이)가 있습니다. 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-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-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": "<strong>치명: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]이 활성화되어 있습니다!</strong>\n이 옵션은 데이터를 입력하는 데 예기치 않는 손상을 일으킵니다.\n이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
+ "config-mbstring": "<strong>치명: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]이 활성화되어 있습니다!</strong>\n이 옵션은 오류가 발생하고 데이터를 입력하는 데 예기치 않는 손상을 일으킬 수 있습니다.\n이 옵션을 비활성화하지 않는 한 미디어위키를 설치하고 사용할 수 없습니다.",
+ "config-safe-mode": "<strong>경고:</strong> PHP의 [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-pcre-old": "<strong>치명:</strong> PCRE $1 또는 그 이상이 필요합니다.\nPHP 바이너리는 PCRE $2에 연결되어 있습니다. [https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE 자세한 정보].",
+ "config-pcre-no-utf8": "<strong>치명:</strong> PHP의 PCRE 모듈은 RCRE_UTF8 지원 없이 컴파일된 것 같습니다.\n미디어위키가 올바르게 작동하려면 UTF-8을 지원해야 합니다.",
"config-memory-raised": "PHP의 <code>memory_limit</code>는 $1이며 $2(으)로 늘렸습니다.",
"config-memory-bad": "'''경고:''' 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": "'''치명:''' 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]가 설치되었습니다",
@@ -90,14 +94,14 @@
"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-help": "데이터베이스 서버가 다른 서버에 있으면 여기에 호스트 이름이나 IP 주소를 입력하세요.\n\n공유하는 웹 호스팅을 사용하고 있으면 호스팅 제공 업체는 올바른 호스트 이름을 설명하고 있을 것입니다.\n\nWindows 서버에 설치하고 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-account-oracle-warn": "데이터베이스 백엔드로 Oracle을 설치하기 위해 지원하는 세 가지 시나리오가 있습니다:\n\n설치 과정의 일부로 데이터베이스 계정을 만들려면 설치를 위해 데이터베이스 계정으로 SYSDBA 역할을 가진 계정을 제공하고 웹 접근 계정에 대해 원하는 자격 증명을 지정하세요, 그렇지 않으면 수동으로 웹 접근 계정을 만들 수 있으며 (스키마 개체를 만들 권한이 필요한 경우) 또는 생성 권한을 가진 계정과 웹 접근이 제한된 계정의 두 가지 다른 계정을 제공할 수도 있습니다\n\n필요한 권한을 가진 계정을 만드는 스크립트는 이 설치 위치의 \"maintenance/oracle/\" 디렉터리에서 찾을 수 있습니다. 제한된 계정을 사용하면 기본 계정의 모든 유지 관리 기능이 비활성화된다는 점에 유의하십시오.",
"config-db-install-account": "설치를 위한 사용자 계정",
"config-db-username": "데이터베이스 사용자 이름:",
"config-db-password": "데이터베이스 비밀번호:",
@@ -122,39 +126,39 @@
"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-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-oracle": "Oracle",
- "config-type-mssql": "마이크로소프트 SQL 서버",
+ "config-type-mssql": "Microsoft 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-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL]은 미디어위키의 기본 대상이며 가장 잘 지원됩니다. 미디어위키는 또한 MySQL와 호환되는 [{{int:version-db-mariadb-url}} MariaDB]와 [{{int:version-db-percona-url}} Percona 서버]에서도 작동합니다. ([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 서버]는 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": "마이크로소프트 SQL 서버 설정",
+ "config-header-mssql": "Microsoft 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-server-oracle": "\"$1\" 데이터베이스 TNS가 잘못됐습니다.\n\"TNS Name\"이나 \"Easy Connect\" 문자열 중 하나를 사용하세요 ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Oracle 네이밍 메서드]).",
"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-create-oracle": "설치 관리자는 새 계정을 만들기 위한 SYSDBA 계정만을 지원합니다.",
"config-db-sys-user-exists-oracle": "\"$1\" 사용자 계정이 이미 존재합니다. SYSDBA는 새 계정을 만드는 데에만 사용할 수 있습니다!",
"config-postgres-old": "PostgreSQL $1 이상이 필요하나 $2(이)가 있습니다.",
- "config-mssql-old": "마이크로소프트 SQL 서버 $1 이상의 버전이 필요합니다. 현재 버전은 $2입니다.",
+ "config-mssql-old": "Microsoft 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-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호스트, 계정 이름과 비밀번호를 확인하고 다시 시도하세요.",
@@ -274,7 +278,9 @@
"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다음 페이지로 진행하세요.",
@@ -306,13 +312,15 @@
"config-install-stats": "통계를 초기화하는 중",
"config-install-keys": "보안 키를 만드는 중",
"config-insecure-keys": "'''경고:''' 설치 중에 생성한 {{PLURAL:$2|보안 키}} ($1)는 완전히 안전하지 {{PLURAL:$2|않습니다}}. 직접 바꾸는 것을 고려하세요.",
+ "config-install-updates": "불필요한 업데이트 실행 방지",
+ "config-install-updates-failed": "<strong>오류:</strong> 다음 오류로 테이블 안에 업데이트 키를 넣기에 실패했습니다: $1",
"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-install-done": "<strong>축하합니다!</strong>\n미디어위키가 성공적으로 설치되었습니다.\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": "확장하려면 클릭",
diff --git a/includes/installer/i18n/ksh.json b/includes/installer/i18n/ksh.json
index 785b2b5e..367db406 100644
--- a/includes/installer/i18n/ksh.json
+++ b/includes/installer/i18n/ksh.json
@@ -19,13 +19,13 @@
"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-your-language": "De Schprohch beim Enreeschte:",
+ "config-your-language-help": "Donn heh di Schprohch ußsöhke, di dat Enschtallzjuhnsprojramm kalle sull.",
+ "config-wiki-language": "Dem Wiki sing Schprohch:",
+ "config-wiki-language-help": "Donn heh di Schprohch ußsöhke, di et Wiki schtandattmääßesch kalle sull.",
"config-back": "← Retuur",
"config-continue": "Wigger →",
- "config-page-language": "Schprooch",
+ "config-page-language": "Schprohch",
"config-page-welcome": "Wellkumme beim MediaWiki!",
"config-page-dbconnect": "Met dä Daatebangk Verbenge",
"config-page-upgrade": "En Inshtallzjuhn op der neuste Shtand bränge",
@@ -48,7 +48,7 @@
"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-env-hhvm": "HHVM $1 es enschtalleerd.",
"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.",
@@ -56,7 +56,8 @@
"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-register-globals-error": "<strong>Fähler: dem PHP sing Enschtällong <code>[http://php.net/register_globals register_globals]</code> es aanjeschalldt.\nSe moß ußjeschalldt sin, domet mer heh wigger maache kann.</strong>\nLoor op dä Sigg [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] wi mer se ußschallde kann.",
+ "config-magic-quotes-gpc": "'''Dä!''' Dem PHP singe Schallder <code lang=\"en\">[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc]</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-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.",
@@ -67,6 +68,7 @@
"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-iconv": "'''Fähler:''' <i lang=\"en\">PHP</i> moß met dä Ongerschtözong för der [http://www.php.net/manual/en/iconv.installation.php <code lang=\"en\">iconv</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.",
@@ -114,7 +116,7 @@
"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-charset-help": "<strong>Opjepaß:</strong>\nWann De et <strong>röckwääts kompatibel UTF-8 Fommaht</strong> nemmps, met dem <i lang=\"en\">MySQL</i> singe Väsjohn 4.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 Fommaht küt, en dä Dahtebangk en binähr kodehrte Dahtefälder faßhallde.\nDat es flöcker un spaasahmer wi et UTF-8 Fommaht vum <i lang=\"en\">MySQL</i> un määd_et müjjelesch, jehdes <i lang=\"en\">Unicode</i>-Zeische met faßzehallde.\n\nBeim Schpeischere em <strong>UTF-8 Fomaht</strong> deihd_et <i lang=\"en\">MySQL</i> der Zeischesaz un de Kodehrung vun dä Dahte känne, un kann se akeraht 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 jrondlähje Knubbel för vill Schprohche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeschpeischert wähde.",
"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:",
@@ -182,7 +184,7 @@
"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-mysql-charset-help": "Beim Schpeischere em <strong>binähre Fomaht</strong> deiht MediaWiki Täx, dä em UTF-8 Fommaht kütt, en singer Dahtebangk en binähr kodehrte Dahtefälder faßhallde.\nDad_es flöcker un spahsamer wi et UTF-8 Fommaht vum <i lang=\"en\">MySQL</i> un määd_et müjjelesch, jehdes <i lang=\"en\">Unicode</i>-Zeische met faßzehallde.\n\nBeim Schpeischere em <strong>UTF-8 Fomaht<strong> deihd_et <i lang=\"en\">MySQL</i> der Zeischesaz un de Kodehrung vun dä Dahte känne, un kann se akeraht 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ähje Knubbel för vill Schprohche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeschpeischert wähde.",
"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.",
@@ -234,7 +236,7 @@
"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-license-help": "Ättlijje öffentleje Wikis donn iehr Beidrääsch onger en [http://freedomdefined.org/Definition freije Lizänz] schtelle.\nDat hellef, e Jeföhl vun Jemeinsamkeid opzeboue, un op lange Seesch 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 dröm han well, dat mer för 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 verschtonn un et Wiggerjävve un widder Bruche es ens schwieerejer 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.",
@@ -274,6 +276,12 @@
"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-skins": "Bedeenbovverfläsche",
+ "config-skins-help": "De opjeleß Beddenbovverfläsche sin en Dingem Verzeischnesß <code>./skins</code> dre. Do moß winneschßdens eine enschallde, un eine för der Schtandatt ußsöhke.",
+ "config-skins-use-as-default": "Donn heh di Bovverfläsch als der Schtandatt nämme.",
+ "config-skins-missing": "Mer han kein bedeebovverfläsche jevonge un nämme dröm der Schtandatt, bes De wälsche enjeresch häß.",
+ "config-skins-must-enable-some": "Do moß winneschßtens ein Beddenbovverfläsch ußsöhke zum aanschallde.",
+ "config-skins-must-enable-default": "De Schtadatt-Beddenbovverfläsch moß och enjeschalldt sin.",
"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",
@@ -303,6 +311,8 @@
"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-updates": "Donn kein onnühdeje Änderonge maache.",
+ "config-install-updates-failed": "<strong>Dä:</strong> Schlößßelle för et Ändere en Tabälle bränge es donävve jajange. Jemäldt wood: $1",
"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.",
@@ -312,6 +322,7 @@
"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-help-tooltip": "Donn Hölp heh aan däm Plaaz enblände.",
"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.'''",
diff --git a/includes/installer/i18n/ku-latn.json b/includes/installer/i18n/ku-latn.json
index a08c83ac..920d9ae2 100644
--- a/includes/installer/i18n/ku-latn.json
+++ b/includes/installer/i18n/ku-latn.json
@@ -6,6 +6,7 @@
},
"config-information": "Agahî",
"config-your-language": "Zimanê te:",
+ "config-wiki-language": "Zimanê wîkiyê:",
"config-page-language": "Ziman",
"config-page-name": "Nav",
"config-page-options": "Vebijêrk",
diff --git a/includes/installer/i18n/lb.json b/includes/installer/i18n/lb.json
index bb1c8295..2dccc50c 100644
--- a/includes/installer/i18n/lb.json
+++ b/includes/installer/i18n/lb.json
@@ -46,7 +46,7 @@
"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-no-db": "Et konnt kee passenden Datebank-Driver fonnt ginn! Dir musst een Datebank-Driver fir PHP installéieren.\n{{PLURAL:$2|Dësn Datebank-Typ gëtt|Dës Datebank-Type ginn}} ënnerstëtzt: $1.\n\nWann Dir PHP selwer compiléiert hutt, da rekonfiguréiert en mat dem ageschalten Datebank-Client, zum Beispill an deem Dir <code>./configure --with-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.",
@@ -160,6 +160,7 @@
"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-user-help": "All Benotzer erlaben sech géigesäiteg E-Mailen ze schécken, wa si dat an hiren Astellungen aktivéiert hunn.",
"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.",
diff --git a/includes/installer/i18n/lzh.json b/includes/installer/i18n/lzh.json
index 190ee047..91353939 100644
--- a/includes/installer/i18n/lzh.json
+++ b/includes/installer/i18n/lzh.json
@@ -5,6 +5,7 @@
]
},
"config-information": "文訊",
+ "config-page-language": "語",
"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/mai.json b/includes/installer/i18n/mai.json
index b52cef79..8302c03c 100644
--- a/includes/installer/i18n/mai.json
+++ b/includes/installer/i18n/mai.json
@@ -1,9 +1,29 @@
{
"@metadata": {
"authors": [
- "Umeshberma"
+ "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]"
+ "config-information": "जानकारी",
+ "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-existingwiki": "रहल विकी",
+ "config-restart": "हँ, एकरा पुन: सुरु कएल जाए",
+ "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/mfe.json b/includes/installer/i18n/mfe.json
new file mode 100644
index 00000000..0cd9b6e3
--- /dev/null
+++ b/includes/installer/i18n/mfe.json
@@ -0,0 +1,45 @@
+{
+ "@metadata": {
+ "authors": [
+ "Moris231"
+ ]
+ },
+ "config-desc": "Programme installasion pu MediaWiki",
+ "config-title": "Installasion MediaWiki $1",
+ "config-information": "Informasion",
+ "config-localsettings-key": "Mis a zour lakle:",
+ "config-localsettings-badkey": "Lakle ki ou inn fourni inkorrekt.",
+ "config-your-language": "Ou langaz:",
+ "config-your-language-help": "Seleksionn enn langaz ki pu servi pendan prosesis installasion.",
+ "config-wiki-language": "Langaz Wiki:",
+ "config-wiki-language-help": "Seleksionn langaz dan ki Wiki pu prinsipalman ekrir.",
+ "config-back": "← Retourne",
+ "config-continue": "Kontinye →",
+ "config-page-language": "Langaz",
+ "config-page-welcome": "Bienvini lor MediaWiki!",
+ "config-page-dbconnect": "Konekte base donnee",
+ "config-page-dbsettings": "Paramets database",
+ "config-page-name": "Nom",
+ "config-page-options": "Opsion",
+ "config-page-install": "Installe",
+ "config-page-complete": "Termine!",
+ "config-page-restart": "Rekoumans installasion",
+ "config-page-readme": "Lir-mwa",
+ "config-page-releasenotes": "Notes verzion",
+ "config-page-copying": "Kopi",
+ "config-page-upgradedoc": "Mis a zour",
+ "config-page-existingwiki": "Wiki existan",
+ "config-restart": "Oui, rekoumans li",
+ "config-env-php": "PHP $1 inn finn installe.",
+ "config-env-hhvm": "HHVM $1 inn finn installe.",
+ "config-diff3-bad": "GNU diff3 introuvab.",
+ "config-db-type": "Type database:",
+ "config-db-host": "Hote database:",
+ "config-db-host-oracle": "Nom TNS database:",
+ "config-db-wiki-settings": "Idantifie sa wiki-la",
+ "config-db-name": "Nom base donnee:",
+ "config-db-name-oracle": "Schema base donnee:",
+ "config-db-install-account": "Kontt litilizater pu sa installasion",
+ "config-db-username": "Itilizater database:",
+ "config-db-password": "Password database:"
+}
diff --git a/includes/installer/i18n/mk.json b/includes/installer/i18n/mk.json
index 8784521e..86494844 100644
--- a/includes/installer/i18n/mk.json
+++ b/includes/installer/i18n/mk.json
@@ -25,7 +25,7 @@
"config-back": "← Назад",
"config-continue": "Продолжи →",
"config-page-language": "Јазик",
- "config-page-welcome": "Добредојдовте на МедијаВики!",
+ "config-page-welcome": "Добре дојдовте на МедијаВики!",
"config-page-dbconnect": "Поврзување со базата",
"config-page-upgrade": "Надградба на постоечката воспоставка",
"config-page-dbsettings": "Нагодувања на базата",
@@ -48,11 +48,10 @@
"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-no-db": "Не можев да најдам соодветен двигател за базата на податоци! Ќе треба да воспоставите двигател за PHP-база.\n{{PLURAL:$2|Поддржан се следниов вид|Поддржани се следниве видови}} бази: $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].",
@@ -92,7 +91,7 @@
"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-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Ако користите заедничко (споделено) вдомување, тогаш вашиот вдомител ќе ви даде конкретно име на база за користење, или пак ќе ви даде да создавате бази преку контролната табла.",
diff --git a/includes/installer/i18n/ms.json b/includes/installer/i18n/ms.json
index 879a330b..7d5fab7f 100644
--- a/includes/installer/i18n/ms.json
+++ b/includes/installer/i18n/ms.json
@@ -5,7 +5,8 @@
"Pizza1016",
"SNN95",
"MaxSem",
- "Aviator"
+ "Aviator",
+ "Macofe"
]
},
"config-desc": "Pemasang MediaWiki",
@@ -53,7 +54,7 @@
"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-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].",
@@ -62,6 +63,7 @@
"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-mod-security": "<strong>Amaran:</strong> Pelayan web anda dihidupkan [http://modsecurity.org/ mod_security]/mod_security2. Kebanyakan konfigurasinya yang umum boleh menimbulkan kesulitan untuk MediaWiki dan perisian-perisian lain yang membolehkan pengguna untuk mengeposkan kandungan yang sewenang-wenang.\nJika boleh, ciri-ciri ini harus dimatikan. Jika tidak, rujuki [http://modsecurity.org/documentation/ dokumentasi mod_security] atau hubungi bantuan hos anda jika anda menghadapi ralat sembarangan.",
"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.",
@@ -87,6 +89,7 @@
"config-oracle-def-ts": "Ruang jadual lalai:",
"config-oracle-temp-ts": "Ruang jadual sementara:",
"config-type-mysql": "MySQL (atau yang serasi)",
+ "config-type-mssql": "Microsoft SQL Server",
"config-header-mysql": "Keutamaan MySQL",
"config-header-postgres": "Keutamaan PostgreSQL",
"config-header-sqlite": "Keutamaan SQLite",
@@ -104,6 +107,7 @@
"config-mysql-charset": "Peranggu aksara pangkalan data:",
"config-mysql-binary": "Perduaan",
"config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Jenis pengesahan:",
"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.",
@@ -117,6 +121,7 @@
"config-admin-password": "Kata laluan:",
"config-admin-password-confirm": "Kata laluan lagi:",
"config-admin-name-blank": "Masukkan nama pengguna pentadbir.",
+ "config-admin-password-blank": "Berikan kata laluan untuk akaun 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.",
@@ -133,6 +138,8 @@
"config-license-gfdl": "Lesen Pendokumenan Bebas GNU 1.3 atau ke atas",
"config-license-pd": "Domain Awam",
"config-email-settings": "Tetapan e-mel",
+ "config-skins": "Rupa",
+ "config-skins-use-as-default": "Gunakan rupa ini sebagai asal",
"config-install-step-done": "siap",
"config-install-step-failed": "gagal",
"config-install-user-alreadyexists": "Pengguna \"$1\" sudah wujud",
diff --git a/includes/installer/i18n/nap.json b/includes/installer/i18n/nap.json
index 1cbe7d56..3a43d901 100644
--- a/includes/installer/i18n/nap.json
+++ b/includes/installer/i18n/nap.json
@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
- "C.R."
+ "C.R.",
+ "Chelin"
]
},
"config-desc": "'O prugramma d'istallazione 'e MediaWiki",
@@ -11,6 +12,197 @@
"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-upgrade-key-missing": "S'è scummigliata n'installazione 'e MediaWiki ch'esisteva già.\nPe' ll'agghiurnà, nzertate pe' piacere sta riga ccà abbascio dint' 'a parta vascia d' 'o <code>LocalSettings.php</code> vuosto:\n\n$1",
+ "config-localsettings-incomplete": "'O file <code>LocalSettings.php</code> esistente pare ca fosse cumpleto a metà.\n'A variabbele $1 nun è mpustata.\nCagnate <code>LocalSettings.php</code> in modo ca sta variabbele fosse mpustata e facite clic ncopp'a \"{{int:Config-continue}}\".",
+ "config-localsettings-connection-error": "S'è truvato n'errore pe' tramente ca se faceva 'a connessione a 'o database ausanno 'e mpustaziune specificate dint'a <code>LocalSettings.php</code>. Pe' piacere curriggite sti mpustaziuni e provate n'ata vota.\n\n$1",
+ "config-session-error": "Errore facenno accumincià 'a sessione: $1",
+ "config-session-expired": "'E date d' 'a sessione pareno ammaturate.\n'E sessiune so' configurate pe na durata 'e $1.\n'A putite aumentà pe' bbìa 'e na mpustazione <code>session.gc_maxlifetime</code> dint' 'o file php.ini.\nRiabbìa 'o prucesso 'e installazione.",
+ "config-no-session": "'E date d' 'a sessione so' state perdute!\nCuntrullate 'o file php.ini vuosto e assicurateve ca 'a <code>session.save_path</code> è stata mpustata ncopp'a na cartella appropriata.",
+ "config-your-language": "'A lengua vosta:",
+ "config-your-language-help": "Scigliete na lengua pe' l'ausà pe' tramente ca se fa 'o prucesso 'installazione.",
+ "config-wiki-language": "Lengua d' 'o wiki:",
+ "config-wiki-language-help": "Scigliete 'a lengua ca sarrà ausàta prevalentemente ncopp' 'a wiki.",
+ "config-back": "← Arreto",
+ "config-continue": "Annanze →",
+ "config-page-language": "Lengua",
+ "config-page-welcome": "Bemmenute a MediaWiki!",
+ "config-page-dbconnect": "Connessione a 'o database",
+ "config-page-upgrade": "Agghiuorna l'istallazione esistente",
+ "config-page-dbsettings": "Mpustaziune d' 'o database",
+ "config-page-name": "Nomme",
+ "config-page-options": "Opziune",
+ "config-page-install": "Installa",
+ "config-page-complete": "Cumpreta!",
+ "config-page-restart": "Riabbìa l'installazione",
+ "config-page-readme": "Lieggeme",
+ "config-page-releasenotes": "Note 'e verziona",
+ "config-page-copying": "Copia",
+ "config-page-upgradedoc": "Agghiurnanno",
+ "config-page-existingwiki": "Wiki esistente",
+ "config-help-restart": "Vulite scancellà tutt' 'e date astipate c'avite nzertato e riabbià 'o prucesso d'installazione?",
+ "config-restart": "Sì, riabbìa",
+ "config-welcome": "=== Cuntrollo 'e ll'ambiente ===\nSarranno eseguite 'e cuntrolle bbase pe' putè vedè si st'ambiente è adatto pe' ne ffà l'installazione 'e MediaWiki.\nArricurdateve d'includere sti nfurmaziune si spiate assistenza ncopp' 'a maniera 'e cumpletà l'installazione.",
+ "config-copyright": "=== Copyright e termine ===\n\n$1\n\nChistu programma è nu software libbero; vuje 'o putite redestribbuì e/o cagnà sott' 'e termine d' 'a licienza GNU GPL ('a Licienza Pubbreca Generale) comme pubbrecata d' 'a Free Software Foundation; o pure 'a verziona 2 d' 'a Licienza, o pure (comme vulite vuje) 'a n'ata verziona cchiù nnova.\n\nChistu programma è destribbuito c' 'a speranza d'essere utile, ma SENZA NISCIUNA GARANZIA; senza manco 'a garanzia p' 'a CUMMERCIABBELETÀ O IDONIETÀ PE' NU SCOPO PARTICULARE.\nIate a vedé 'a GNU GPL pe' n'avé cchiù nfurmaziune.\n\nCu stu programma avísseve 'a ricevere <doclink href=Copying>na copia d' 'a Licienza GNU GPL</doclink> cu stu prugramma; si nò, scrivete â Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA o [http://www.gnu.org/copyleft/gpl.html liggite sta paggena ncopp' 'a l'Internet].",
+ "config-sidebar": "* [//www.mediawiki.org Paggina prencepale MediaWiki]\n* [//www.mediawiki.org/wiki/Aiuto:Guida a 'e cuntenute pe' l'utente]\n* [//www.mediawiki.org/wiki/Manuale:Guida a 'e cuntenute pe l'ammenistrature]\n* [//www.mediawiki.org/wiki/Manuale:FAQ FAQ]\n----\n* <doclink href=Readme>Lieggeme</doclink>\n* <doclink href=ReleaseNotes>Note 'e verziona</doclink>\n* <doclink href=Copying>Copie</doclink>\n* <doclink href=UpgradeDoc>Agghiurnamento</doclink>",
+ "config-env-good": "L'ambiente è stato cuntrullato.\nÈ pussibbele installare MediaWiki.",
+ "config-env-bad": "L'ambiente è stato cuntrullato.\nNun se può installà MediaWiki.",
+ "config-env-php": "PHP $1 è installato.",
+ "config-env-hhvm": "HHVM $1 è installato.",
+ "config-unicode-using-utf8": "Aúsa Brion Vibber's utf8_normalize.so pe' ne fà 'a normalizzazione Unicode.",
+ "config-unicode-using-intl": "Aúsa [http://pecl.php.net/intl l'estensione PECL intl] pe' ne fà 'a normalizzazione Unicode.",
+ "config-unicode-pure-php-warning": "<strong>Attenziò:</strong> L' [http://pecl.php.net/intl estensione intl PECL] nun è a disposizione pe' gestire 'a normalizzazione Unicode, accussì se ausasse n'imprementazziona llenta 'n puro PHP.\nSi state a gestire nu pizzo ad alto traffico, avisseve a lieggere cocche considerazione ncopp' 'a [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizzaziona Unicode].",
+ "config-unicode-update-warning": "<strong>Attenziò:</strong> 'A verziona installata 'e normalizzazione Unicode aùsa 'a verziona viecchia d' 'o [http://site.icu-project.org/ pruggetto ICU].\nV'avite 'a [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations agghiurnà] si state a penzà ncopp' 'o fatto d'ausà Unicode.",
+ "config-no-db": "Nun se può truvà nu driver adatto p' 'o database! È necessario installare nu driver p' 'o PHP.\n{{PLURAL:$2|'O furmatto suppurtato|'E furmatte suppurtate}} 'e database ccà annanze: $1.\n\nSi cumpilate PHP autonomamente, riaccunciatevello attivando nu client database, p'esempio ausannoo <code>./configure --with-mysqli</code>.\nQuanno fosse installato PHP pe' bbìa 'e nu pacchetto Debian o Ubuntu, allora avite 'a installà pure 'o pacchetto <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "'''Attenziò''': tenite 'o SQLite $1 pe' tramente ca ce vulesse 'a verziona $2, SQLite nun sarrà a disposizione.",
+ "config-no-fts3": "'''Attenziò''': SQLite è cumpilato senza 'o [//sqlite.org/fts3.html modulo FTS3], 'e funziune 'e p'ascià dinto nun sarranno a disposizione ncopp'a stu backend.",
+ "config-register-globals-error": "<strong>Errore: l'opzione PHP <code>[http://php.net/register_globals register_globals]</code> è apicciata.\nS'avesse 'a stutà pe' cuntinuà c' 'a installazione.</strong>\nVide [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] pe' n'avé n'aiuto ncopp'a comme s'avess'a ffà.",
+ "config-magic-quotes-gpc": "<strong>Fatale: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] è attivo!</strong>\nChest'opzione scassa 'e date d'input 'n modo scanusciuto.\nNun putite installare o utilizzare MediaWiki, si nun stutate st'opzione.",
+ "config-magic-quotes-runtime": "<strong>Fatale: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] è attivo!'</strong>\nSt'opzione scassa 'e date 'e na manera scanusciuta.\nNun se può installà o ausà MediaWiki si nun se stuta st'opzione.",
+ "config-magic-quotes-sybase": "<strong>Fatale: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] è attivo!'</strong>\nSt'opzione scassa 'e date 'e na manera scanusciuta.\nNun se può installà o ausà MediaWiki si nun se stuta st'opzione.",
+ "config-mbstring": "<strong>Fatale: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] è attivo!'</strong>\nSt'opzione scassa 'e date 'e na manera scanusciuta.\nNun se può installà o ausà MediaWiki si nun se stuta st'opzione.",
+ "config-safe-mode": "<strong>Warning:</strong> PHP's [http://www.php.net/features.safe-mode safe mode] è attivato.\nPutesse fà cocche probblema, specialmente si state ausanno 'a funziona 'e carrecà file e 'o supporto d' ' e funziune <code>math</code>.",
+ "config-xml-bad": "'O modulo XML 'e PHP è mancante.\nA MediaWiki servessero 'e funziune prisente dint'a stu modulo e nun faticarrà c' 'a configurazione 'e mò.\nSi se sta eseguenno Mandrake, installare 'o pacco php-xml.",
+ "config-pcre-old": "<strong>Errore fatale:</strong> s'addimanna PCRE $1 o succiessivo.\n'O file vuosto binario PHP è acucchiato c' 'o PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Cchiù nfurmaziune].",
+ "config-pcre-no-utf8": "<strong>Fatale:</strong> 'E module PCRE d' 'o PHP pare ca se so' compilate senza PCRE_UTF8 supporto.\nA MediaWiki serve nu supporto UTF-8 pe' putè funziunà apposto.",
+ "config-memory-raised": "'O valore 'e PHP <code>memory_limit</code> è $1, aumentato a $2.",
+ "config-memory-bad": "<strong>Attenziò:</strong> 'o valore 'e PHP <code>memory_limit</code> è $1.\nProbabbilmente troppo basso.\n'A installazione se putesse scassà!",
+ "config-ctype": "'''Errore''': 'o PHP s'adda ghienchere c' 'o supporto pe' l'[http://www.php.net/manual/it/ctype.installation.php estensione Ctype].",
+ "config-iconv": "<strong>Fatale:</strong> PHP s'adda ghienchere c' 'o supporto pe' l'[http://www.php.net/manual/en/iconv.installation.php estensione iconv].",
+ "config-json": "'''Errore:''' PHP è stato compilato senza 'o supporto pe' JSON. E' necessario installà l'estensione PHP pe' JSON o l'estensione [http://pecl.php.net/package/jsonc PECL jsonc] apprimm' 'e installà MediaWiki.\n* L'estensione PHP sta dint'a Red Hat Enterprise Linux (CentOS) 5 e 6, ma s'avess'abbià 'n <code>/etc/php.ini</code> o <code>/etc/php.d/json.ini</code>.\n* Cocche distribuzione 'e Linux pubblicata aropp'a majo 'e 2013 lèvano l'estensione PHP, e a pizzo sujo aúsano l'estensione PECL comme <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": "'''Attenziò:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache] nun so' state truvate.\n'A funziona caching 'e ll'oggette non è apicciata.",
+ "config-mod-security": "<strong>Attenziò:</strong> 'O servitore web vuosto téne [http://modsecurity.org/ mod_security]/mod_security2 appicciato. Ce stanno tante mpustaziune commune ca 'o facessero causà prubbleme a MediaWiki e ll'ati software ca permettessero ll'utente 'e pubbrecà cuntenute.\nSi putite, stutate sta funziona. Sinò, riferite 'a [http://modsecurity.org/documentation/ documentaziona ncopp' 'o mod_security] o cuntattate 'o host vuosto pe' ve dà supporto quanno se scummogliasse cocch'errore.",
+ "config-diff3-bad": "GNU diff3 nun truvato.",
+ "config-git": "Truvato software 'e cuntrollo d' 'a verziona Git: <code>$1</code>.",
+ "config-git-bad": "Software 'e cuntrollo d' 'a verziona Git nun truvato.",
+ "config-imagemagick": "Truvato ImageMagick: <code>$1</code>.\n'E miniature d' 'e fiùre sarranno prisente si l'upload song'abbiàte.",
+ "config-gd": "Truvata 'a bibblioteca ntegrata GD Graphics.\n'E miniature 'e ll'immaggene sarranno prisente si l'upload se song'abbiàte.",
+ "config-no-scaling": "Nun se può truvà 'a bibblioteca GD o ImageMagick.\n'E miniature 'e l'immaggene sarranno stutate.",
+ "config-no-uri": "<strong>Errore:</strong> Nun se può determina l'URI 'e mmò.\nInstallazione spezzata.",
+ "config-no-cli-uri": "'''Attenziò''': <code>--scriptpath</code> nun specificato, s'aùsa 'o valore predefinito: <code>$1</code>.",
+ "config-using-server": "Nomme d' 'o server ca se stà ausanno \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "URL d' 'o server ca se stà ausanno \"<nowiki>$1$2</nowiki>\".",
+ "config-uploads-not-safe": "<strong>Attenziò:</strong> 'a cartella predefinita p' 'e carreche <code>$1</code> è vulnerabbele all'esecuzione arbitraria 'e script.\nPure si MediaWiki cuntrolla tutt' 'e file carrecate pe' rischio 'a sicurezza se raccumanna 'e [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security nchiure sta vulnerabbilità 'e sicurezza] apprimm'appiccià 'a funcione 'e carreche.",
+ "config-no-cli-uploads-check": "<strong>Attenziò:</strong> 'a cartella predefinita p' 'e carreche (<code>$1</code>) nun è stata cuntrullata p' 'a vulnerabbelità ncopp'a l'esecuzione arbitraria 'e script pe' tramente ca se fà l'installazione 'a linea 'e commando.",
+ "config-brokenlibxml": "'O sistema vuosto ave na combinazione 'e verziune 'e PHP e libxml2 nguacchiata ca putesse scassà 'e date 'e MediaWiki 'n manera annascunnusa e pure l'ati apprecaziune p' 'o web.\nAgghiurnate a libxml2 2.7.3 o cchiù muderno ([https://bugs.php.net/bug.php?id=45996 'o bug studiato d' 'o lato PHP]).\nInstallaziona spezzata.",
+ "config-suhosin-max-value-length": "Suhosin è installato e miette lemmeto 'o parametro GET <code>length</code> a $1 byte.\n'O componente MediaWiki ResourceLoader funzionarrà aggirann'a stu lemmeto, ma luvanno prestaziune.\nSi pussibile, avit'a mpustà <code>suhosin.get.max_value_length</code> a 1024 o cchiù auto 'n <code>php.ini</code>, e mpustà <code>$wgResourceLoaderMaxQueryLength</code> a 'o stesso valore 'n <code>LocalSettings.php</code>.",
+ "config-db-type": "Tipo 'e database:",
+ "config-db-host": "Host d' 'o database:",
+ "config-db-host-help": "Si 'o server database vuosto stà mpizzato dint' 'a nu server differente, miette 'o nomme d' 'o host o l'indirizzo IP sujo.\n\nSi state ausanno nu servizio spartuto web hosting, 'aggenzia 'e hosting v'avess'a dà 'o nomme buono 'e nomme host dint' 'a documentaziona suoja.\n\nSi state installanno chisto dint'a nu server Windows cu MySQL, ausanno \"localhost\" può darse ca nun funziona p' 'o nomme server. Si chisto nun funziona, mettite \"127.0.0.1\" p' 'o ndirizzo locale vuosto.\n\nSi state ausanno PostgreSQL, lassate abbacante stu campo pe' v'accucchià cu nu socket Unix.",
+ "config-db-host-oracle": "TNS d' 'o database:",
+ "config-db-host-oracle-help": "Mettite nu valido [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Nomme 'e conessione lucale]; nu file \"tnsnames.ora\" adda essere vesibbele dint'a sta installazione.<br />Si state ausanno 'a libbreria cliente 10g o cchiù ricente putite pure ausà 'o metodo 'e denominazione [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
+ "config-db-wiki-settings": "Identifica stu wiki",
+ "config-db-name": "Nomme d' 'o database:",
+ "config-db-name-help": "Sciglite nu nomme ca identificasse 'a wiki vosta.\nNun avess'a cuntenè spazie.\n\nSi ausate nu web hosting spartuto, 'o furnitore d' 'o hosting v'avesse 'a specificà nu nomme 'e database specifico pe' ve permettere 'e crià database pe' bbìa 'e nu pannello 'e cuntrollo.",
+ "config-db-name-oracle": "Schema d' 'o database:",
+ "config-db-account-oracle-warn": "Ce stanno tre scenarie suppurtate p' 'a installazione d'Oracle comme database 'e backend:\n\nSi vulite crià n'utenza 'e database comme parte d' 'o prucesso 'e installazione, dàte nu cunto c' 'o ruolo SYSDBA comme utenza d' 'o database pe ne fà installazione e specificate 'e credenziale vulute pe' ne fà l'utenza d'acciesso web, sinò è possibbele crià manualmente l'utenza d'accesso web e dà surtanto chillu cunto (si tenite autorizzaziune neccessarie pe' crià oggette 'e stu schema) po dà dduje utenze divierze, una ch' 'e permesse 'e criazione e n'ata pe ne putè trasì ô web.\n\n'O script p' 'a criazione 'e n'utenza cu tutte st'autorizzaziune neccessarie 'o putite truvà dint' 'a cartella \"maintenance/oracle\" 'e sta installazione. Tenite a mmente che l'uso 'e n'utenza cu sti restriziune stutarrà tutt' 'e funziune 'e manutenzione c' 'o cunto predefinito.",
+ "config-db-install-account": "Cunto utente pe' l'installazione",
+ "config-db-username": "Nomme utente p' 'o database:",
+ "config-db-password": "Password d' 'o database:",
+ "config-db-password-empty": "Avita nzertà na password pe' l'utente nuovo d' 'o database: $1.\nPure si fosse possibbele 'e crià ll'utente senza password chisto nun fosse sicuro.",
+ "config-db-username-empty": "Avita miette nu valore p' 'o \"{{int:config-db-username}}\"",
+ "config-db-install-username": "Nzertate 'o nomme utente ca s'aussarrà pe' ve cullegà ô database pe' tramente ca se fà l'installazione. Chistu nun è 'o nomme utente d' 'o cunto MediaWiki; ma chillo p' 'o database vuosto.",
+ "config-db-install-password": "Nzertate 'a password che s'ausarrà pe' ve putè cullegà ô database pe' tramente ca se fa l'installazione.\nChista nun è 'a password d' 'o cunto 'e MediaWiki; ma chilla p' 'o database vuosto.",
+ "config-db-install-help": "Miette 'o nomme utente e 'a password ca sarrà usata quanno ve cullegate ô database pe' tramente ca facite 'a installazione.",
+ "config-db-account-lock": "Aúsa 'o stisso nomme utente e password pe' l'operazione normale.",
+ "config-db-wiki-account": "Account utente p' 'o funzionamento nurmale",
+ "config-db-wiki-help": "Miette 'o nomme utente e 'a password ca sarrà ausata pe' se cullegà ô database pe' l'operazione normale d' 'o wiki. Si 'o cunto nun esiste, e 'o cunto e installazione téne diritte sufficiente, sarrà criato ch' 'e diritte minime necessarie pe' putè faticà ncopp' 'o wiki.",
+ "config-db-prefix": "Prefisso d' 'a tavolozza d' 'o database:",
+ "config-db-prefix-help": "Si tenite abbesuogno 'e spartì nu database nfra cchiù wiki, o nfra MediaWiki e n'at'apprecazione web, putite scegliere d'azzeccà nu prefisso a tutte 'e nomme 'e tabbella, pe putè evità cunflitte.\nNun ausate abbacante.\n\n'O solito, stu campo se lassasse abbacante.",
+ "config-db-charset": "Nzieme 'e carattere d' 'o 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 cu compatibbelità UTF-8",
+ "config-charset-help": "<strong>Attenziò:</strong> Si state ausanno <strong>backwards-compatible UTF-8</strong> ncopp' 'o MySQL 4.1+, e po' se fà 'o backup d' 'o databas c' 'o codece <code>mysqldump</code>, putesse scassà tutt' 'e carattere nun-ASCII, sfunnanno irreversibbilmente sti backup!\n\nDint' 'a <strong>modalità binaria</strong>, MediaWiki astipa 'o testo UTF-8 dint' 'o database 'n campe binarie.\nChest'è assaje cchiù efficiente rispett' 'a modalità UTF-8 'e MySQL, e te premmettesse 'ausà 'a gamma cumpreta 'e carattere unicode.\nDint' 'o <strong>UTF-8 mode</strong>, MySQL canoscerrà dint'a qualu set 'e carattere stanno 'e date vuoste, putenn'accussì apprisentà e cagnà chiste int'a nu modo appropriato,\nchesto però nun te lassarrà astipà 'e carattere pe' copp' 'o [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ "config-mysql-old": "MySQL $1 o cchiù muderno è necessario. Vuje avite $2.",
+ "config-db-port": "Porta d' 'o database:",
+ "config-db-schema": "Schema pe' MediaWiki:",
+ "config-db-schema-help": "Stu schema 'n genere sarrà buono.\nSi 'o vulite cagnà facite sulamente si ne tenite abbesuogno.",
+ "config-pg-test-error": "Nun se può connettà a 'o database <strong>$1</strong>: $2",
+ "config-sqlite-dir": "Cartella 'e data 'e SQLite:",
+ "config-sqlite-dir-help": "SQLite astipa tutte 'e date dint'a n'uneco file.\n\n'A cartella ca starraje a innecà adda essere scrivibbele d' 'o server webe pe' tramente ca sta l'istallazione.\n\nAvess'a essere <strong>nun trasibbele via web</strong>, è pecchesto ca nun se sta mettenno addò stanno 'e file PHP.\n\nL'installatore scriverrà nzieme a chesta nu file <code>.htaccess</code>, ma si 'o tentativo scassasse, coccheruno putesse tenè acciesso dint' 'o database ncruro.\nChesto pure cunzidera 'e date ncruro 'e ll'utente (indirizze, password cifrate) accussì comme 'e verziune luvate e ati dati d'accesso limmetato dint' 'o wiki.\n\nCunzidera ll'opportunità 'e sistimà ô tiempo 'o database 'a n'ata parte, p'esempio int'a <code>/var/lib/mediawiki/tuowiki</code>.",
+ "config-oracle-def-ts": "Tablespace 'e default:",
+ "config-oracle-temp-ts": "Tablespace temporaneo:",
+ "config-type-mysql": "MySQL (o compatibbele)",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-support-info": "MediaWiki supporta 'e sisteme 'e database ccà abbascio:\n\n$1\n\nSi nfra chiste ccà nun vedite 'o sistema 'e database ca vulite ausà, allora avite liegge 'e instruziune ccà ncoppa pe' ne dà supporto.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] è 'a configurazione cchiù mmeglio p' 'o MediaWiki e è chilla meglio suppurtata. MediaWiki può faticà pure cu' [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], ca fossero MySQL cumpatibbele. ([http://www.php.net/manual/en/mysqli.installation.php Comme s'adda fà pe' cumpilà PHP cu suppuorto MySQL])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] è nu sistema canusciuto 'e database open source ca fosse n'alternativa a MySQL. Putess'avé cocch'errore p'arricettà, e nun è cunzigliato 'e ll'ausà dint'a n'ambiente 'e produziona. ([http://www.php.net/manual/en/pgsql.installation.php Comme s'avess'a cumpilà PHP cu suppuorto PostgreSQL])",
+ "config-header-mysql": "Mpustaziune MySQL",
+ "config-header-postgres": "Mpustaziune PostgreSQL",
+ "config-header-sqlite": "Mpustaziune SQLite",
+ "config-header-oracle": "Mpustaziune Oracle",
+ "config-header-mssql": "Mpustaziune 'e Microsoft SQL Server",
+ "config-invalid-db-type": "'O tipo 'e database nun è buono.",
+ "config-missing-db-name": "Avita miette nu valore p' 'o \"{{int:config-db-name}}\"",
+ "config-missing-db-host": "Avita miette nu valore p' 'o \"{{int:config-db-host}}\"",
+ "config-missing-db-server-oracle": "Avita miette nu valore p' 'o \"{{int:config-db-host-oracle}}\"",
+ "config-invalid-db-server-oracle": "'O database 'e TNS \"$1\" nun è buono.\nAusate 'o \"TNS Name\" o na catena d' \"Easy Connect\"([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Metude 'e Nommena Oracle]).",
+ "config-connection-error": "$1.\n\nCuntrullate 'o host, nomme utente e password e tentate n'ata vota.",
+ "config-invalid-schema": "Schema MediaWiki \"$1\" nun è buono.\nAusate surtanto 'e lettere ASCII (a-z, A-Z), nummere (0-9) e carattere 'e sottolineatura (_).",
+ "config-db-sys-create-oracle": "'O prugramma 'e installazione supporta surtanto l'uso 'e nu cunto SYSDBA pe' putè crià nu cunto nuovo.",
+ "config-postgres-old": "PostgreSQL $1 o cchiù muderno è necessario. Vuje tenite $2.",
+ "config-mssql-old": "Microsoft SQL Server $1 o cchiù muderno è necessario. Vuje tenite $2.",
+ "config-sqlite-name-help": "Sciglite nu nomme ca identificasse 'o wiki vuosto.\nNun ausà spazie o trattine.\nChesto serverrà pe' putè miettere 'o nomme ro file 'e date SQLite.",
+ "config-sqlite-parent-unwritable-group": "Nun se pò crià 'a cartella 'e date <code><nowiki>$1</nowiki></code>, pecché 'a cartella supiriore <code><nowiki>$2</nowiki></code> nun se pò scrivere 'a 'o webserver.\n\n'O prugramma d'installazione ha determinato l'utente c' 'o quale 'o server web se stà a esecutà.\nDàte 'a pussibbelità 'e scrivere dint' 'a cartella <code><nowiki>$3</nowiki></code> pe' cuntinuà\nNcopp'a nu sistema Unix/Linux:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-connection-error": "$1.\n\nCuntrullate 'a cartella 'e date e 'o nomme d' 'o database ccà abbascio e pruvate n'ata vota.",
+ "config-sqlite-readonly": "'O file <code>$1</code> nun è scrivibbele.",
+ "config-sqlite-cant-create-db": "Nun se può crià 'o file database <code>$1</code>.",
+ "config-sqlite-fts3-downgrade": "'O PHP è mancante d' 'o suppuorto FTS3, declassamento tabbelle 'n curzo",
+ "config-can-upgrade": "Nce stanno tabbelle 'e MediaWiki int'a stu database.\nPe ll'agghiurnà a MediaWiki $1, facite click ncopp' 'a '''continua'''.",
+ "config-upgrade-done": "Agghiurnamiento cumpreto.\n\nMo' putite [$1 accummincià 'ausà 'o wiki vuosto].\n\nSi vulite ringignà 'o file vuosto 'e <code>LocalSettings.php</code>, cliccate ncopp' 'o buttone ccà abbascio. St'operazione '''nun è cunzigliata''', si nun è pecché forse tenite cocche prubblema c' 'o wiki vuosto.",
+ "config-upgrade-done-no-regenerate": "Agghiurnamiento cumpreto.\n\nPutite [$1 accummincià 'ausà 'o wiki vuosto].",
+ "config-regenerate": "Rigennera LocalSettings.php →",
+ "config-show-table-status": "'A query <code>SHOW TABLE STATUS</code> è fallita!",
+ "config-unknown-collation": "<strong>Attenziò:</strong> 'O database sta ausanno reule 'e cunfronto nun ricanusciute.",
+ "config-db-web-account": "Account d' 'o database pe' ne fà acciesso web",
+ "config-db-web-help": "Scigliete 'o nomme utente e passwrod ca 'o web server ausarrà pe' se cullegà 'o server database, pe' tramente ca se fa' operazione normale d' 'o wiki.",
+ "config-db-web-account-same": "Aúsa 'o stisso cunto comme quanno s'è fatta 'a installazione",
+ "config-db-web-create": "Crìa 'o cunto si nun esiste ancora",
+ "config-db-web-no-create-privs": "'O cunto ausato pe' ne fà l'installazione nun tene diritte necessarie pe' ne putè crià n'atu cunto.\n'O cunto zegnàto ccà adda esistere già.",
+ "config-mysql-engine": "Mutore d'astipo:",
+ "config-mysql-innodb": "InnoDB",
+ "config-mysql-myisam": "MyISAM",
+ "config-mysql-charset": "Nzieme 'e carattere d' 'o database:",
+ "config-mysql-binary": "Binario",
+ "config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Tipo d'autenticazione:",
"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."
+ "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' 'a wiki.\nSi piglie \"{{int:config-mssql-windowsauth}}\", 'e credenziale 'e qualunque fosse ll'utenza ca 'o webserver sta pruciessanno sarranno ausate.",
+ "config-mssql-sqlauth": "Autenticazione 'e SQL Server",
+ "config-mssql-windowsauth": "Autenticazione 'e Windows",
+ "config-site-name": "Nomme d' 'o wiki:",
+ "config-site-name-help": "Chisto cumparerrà dint' 'a barra d' 'o titolo d' 'o navigatore e pure dint'a n'ati pizze.",
+ "config-site-name-blank": "Scrive 'o nomme d' 'o sito.",
+ "config-project-namespace": "Namespace d' 'o pruggetto:",
+ "config-ns-generic": "Pruggetto",
+ "config-ns-site-name": "'O stesso ch' 'o nomme d' 'o wiki: $1",
+ "config-ns-other": "Ati (specificà)",
+ "config-ns-other-default": "MyWiki",
+ "config-ns-invalid": "'O namespace specificato \"<nowiki>$1</nowiki>\" nun è buono.\nSpecificate nu namespace 'e pruggetto differente.",
+ "config-ns-conflict": "'O namespace innecato \"<nowiki>$1</nowiki>\" tràse ncunflitto cu nu namespace predefinito 'e MediaWiki.\nSpecifiate n'atu nomme divierzo 'e namespace 'e pruggetto.",
+ "config-admin-box": "Cunto ammenistratore",
+ "config-admin-name": "'O nomme utente vuosto:",
+ "config-admin-password": "'A password vuosta:",
+ "config-admin-password-confirm": "Password n'ata vota:",
+ "config-admin-help": "Mettite 'o nomme utente ca vuje vulite ccà, p'esempio \"Gennaro Esposito\".\nChest'è 'o nomme ca vuje auserrate pe' trasì 'o wiki.",
+ "config-admin-name-blank": "Mettite nu nomme utente p' 'ammenistratore.",
+ "config-admin-name-invalid": "'O namespace specificato \"<nowiki>$1</nowiki>\" nun è buono.\nSpecificate nu namespace differente.",
+ "config-admin-password-blank": "Miette na password p' 'o cunto d'ammenistratore.",
+ "config-admin-email": "Indirizzo e-mail:",
+ "config-admin-error-bademail": "Avite miso n'indirizzo e-mail invalido.",
+ "config-optional-continue": "Spiate cchiù dimanne.",
+ "config-profile": "Profilo 'e deritte utente:",
+ "config-profile-wiki": "Wiki araputo",
+ "config-profile-no-anon": "Cunto utente obbligatorio",
+ "config-profile-fishbowl": "Surtanto ll'editure premmesse",
+ "config-profile-private": "Wiki privato",
+ "config-license-pd": "Pubbreco duminio",
+ "config-logo": "URL d\"o logo:",
+ "config-cc-again": "Selezziona 'e novo...",
+ "config-install-step-done": "fatto",
+ "config-install-updates": "Mpiccià ll'agghiurnamiente ca nun fossero necessarie",
+ "config-help": "ajùto"
}
diff --git a/includes/installer/i18n/nb.json b/includes/installer/i18n/nb.json
index f3cf6454..26772e98 100644
--- a/includes/installer/i18n/nb.json
+++ b/includes/installer/i18n/nb.json
@@ -55,7 +55,7 @@
"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-no-db": "Fant ingen passende databasedriver! Du må installere en databasedriver for PHP.\nFølgende {{PLURAL:$2|databasetype|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.",
@@ -220,8 +220,8 @@
"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-almost-done": "Du er nesten ferdig!\nDu kan velge å hoppe over de siste konfigurasjonstrinnene og installere wikien med en gang.",
+ "config-optional-continue": "Still meg flere spørsmål.",
"config-optional-skip": "Jeg er lei, bare installer wikien.",
"config-profile": "Brukerrettighetsprofil:",
"config-profile-wiki": "Åpen wiki",
@@ -231,14 +231,14 @@
"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-cc-by-sa": "Creative Commons Navngivelse-DelPåSammeVilkår",
+ "config-license-cc-by": "Creative Commons Navngivelse",
+ "config-license-cc-by-nc-sa": "Creative Commons Navngivelse-IkkeKommersiell-DelPåSammeVilkår",
+ "config-license-cc-0": "Creative Commons Zero (Fristatus-erklæring)",
"config-license-gfdl": "GNU Free Documentation License 1.3 eller senere",
- "config-license-pd": "Offentlig rom",
+ "config-license-pd": "Offentlig eiendom",
"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-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 <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia brukte tidligere GNU Free Documentation License.\nGFDL er en grei lisens, med vanskelig å forstå.\nDet er også vanskelig å gjenbruke innhold lisensiert under GFDL.",
"config-email-settings": "E-postinnstillinger",
"config-enable-email": "Aktiver utgående e-post",
"config-enable-email-help": "Hvis du vil at e-post skal virke må [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.",
@@ -278,6 +278,12 @@
"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-skins": "Drakt",
+ "config-skins-help": "Draktene som listes opp nedenfor ble funnet i <code>./skins</code>-mappen din. Du må slå på minst én, og velge en standarddrakt.",
+ "config-skins-use-as-default": "Bruk denne drakta som standard",
+ "config-skins-missing": "Ingen drakter ble funnet; MediaWiki vil bruke en reservedrakt til du har installert noen ordentlige drakter.",
+ "config-skins-must-enable-some": "Du må velge minst én drakt å slå på.",
+ "config-skins-must-enable-default": "Standarddrakten må være slått på.",
"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",
@@ -307,6 +313,8 @@
"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-updates": "Forhindre unødvendige oppdateringer",
+ "config-install-updates-failed": "<strong>Feil:</strong> Innsetting av oppdateringsnøkler i tabellene mislyktes med følgende feilmelding: $1",
"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.",
diff --git a/includes/installer/i18n/ne.json b/includes/installer/i18n/ne.json
index 836e0fcb..b3d40d22 100644
--- a/includes/installer/i18n/ne.json
+++ b/includes/installer/i18n/ne.json
@@ -13,6 +13,11 @@
"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-name": "नाम",
"config-page-options": "विकल्पहरु",
"config-page-install": "स्थापना गर्ने",
@@ -20,6 +25,7 @@
"config-page-restart": "स्थापना फेरि सुरु गर्ने",
"config-page-readme": "पढ्नुहोस्",
"config-page-releasenotes": "प्रकाशन टिप्पणी",
+ "config-help": "सहायता",
"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.json b/includes/installer/i18n/nl.json
index 5bf16456..d7bd9a67 100644
--- a/includes/installer/i18n/nl.json
+++ b/includes/installer/i18n/nl.json
@@ -54,17 +54,20 @@
"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-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 Veelgestelde 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-env-hhvm": "HHVM $1 is 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-no-db": "Het was niet mogelijk een geschikte databasedriver te vinden voor PHP! U moet een databasedriver installeren voor PHP.\n{{PLURAL:$2|Het volgende databasetype wordt|De volgende databasetypes worden}} ondersteund: $1.\n\nAls u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysqli</code>.\nAls u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook bijvoorbeeld de module <code>php5-mysql</code>.",
"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-register-globals-error": "<strong>Fout: de optie <code>[http://php.net/register_globals register_globals]</code> van PHP is ingeschakeld.\nDeze optie moet uitgeschakeld zijn om door te kunnen gaan met de installatie.</strong>\nOp de pagina [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] staat beschreven hoe u dit kunt doen.",
+ "config-magic-quotes-gpc": "<strong>Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] is actief!<strong>\nDeze instelling zorgt voor onvoorspelbare gegevenscorruptie.\nU kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
"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.",
@@ -75,6 +78,7 @@
"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-iconv": "<strong>Onherstelbare fout:</strong> PHP moet gecompileerd zijn met ondersteuning voor de [http://www.php.net/manual/en/iconv.installation.php uitbreiding iconv].",
"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",
@@ -138,11 +142,11 @@
"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-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 moet zijn met ondersteuning voor SQLite]; gebruikt PDO).",
"config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] is een commerciële database voor grote bedrijven ([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-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] is een commerciële enterprisedatabase voor Windows ([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",
@@ -186,11 +190,11 @@
"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-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 beschadigd 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-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 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-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.",
@@ -242,7 +246,7 @@
"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-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 <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nDe GNU Free Documentation License is de oude licentie voor inhoud uit Wikipedia.\nDit is nog steeds een geldige licentie, maar deze licentie is lastig te begrijpen.\nHet is ook lastig inhoud te hergebruiken onder de GFDL.",
"config-email-settings": "E-mailinstellingen",
"config-enable-email": "Uitgaande e-mail inschakelen",
"config-enable-email-help": "Als u wilt dat e-mailen mogelijk is, dan moeten de [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.",
@@ -283,7 +287,11 @@
"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-help": "De hierboven weergegeven uiterlijken zijn aangetroffen in de map <code>./skins</code>. U moet tenminste één uiterlijk inschakelen en het standaard uiterlijk kiezen.",
"config-skins-use-as-default": "Als standaard vormgeving instellen",
+ "config-skins-missing": "Er zijn geen uiterlijken aangetroffen. MediaWiki gebruikt een basisuiterlijk totdat u een uiterlijk installeert.",
+ "config-skins-must-enable-some": "U moet minstens één vormgeving kiezen om in te schakelen.",
+ "config-skins-must-enable-default": "De vormgeving gekozen als standaard moet ingeschakeld zijn.",
"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",
@@ -313,6 +321,8 @@
"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-updates": "Voorkomen dat updates onnodig worden uitgevoerd",
+ "config-install-updates-failed": "<strong>Fout:</strong> het toevoegen van updatesleutels aan tabellen is mislukt met de volgende fout: $1",
"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.",
diff --git a/includes/installer/i18n/oc.json b/includes/installer/i18n/oc.json
index 372058f8..98d54ac4 100644
--- a/includes/installer/i18n/oc.json
+++ b/includes/installer/i18n/oc.json
@@ -166,12 +166,15 @@
"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-grant-failed": "Fracàs al moment de l'apondon de permissions a 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": "Creacion de la pagina principala amb un contengut per defaut",
+ "config-install-extension-tables": "Creacion de taulas per las extensions activadas",
"config-install-mainpage-failed": "Impossible d’inserir la pagina principala : $1",
"config-download-localsettings": "Telecargar <code>LocalSettings.php</code>",
"config-help": "ajuda",
diff --git a/includes/installer/i18n/pa.json b/includes/installer/i18n/pa.json
index c129ab96..75f73d3b 100644
--- a/includes/installer/i18n/pa.json
+++ b/includes/installer/i18n/pa.json
@@ -1,13 +1,35 @@
{
"@metadata": {
"authors": [
- "Aalam"
+ "Aalam",
+ "Babanwalia"
]
},
"config-information": "ਜਾਣਕਾਰੀ",
"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-copying": "ਨਕਲ",
+ "config-page-upgradedoc": "ਮਿਆਰ-ਉਚਾਈ",
+ "config-page-existingwiki": "ਮੌਜੂਦਾ ਵਿਕੀ",
+ "config-restart": "ਹਾਂਜੀ, ਮੁੜ ਸ਼ੁਰੂ ਕਰੋ",
+ "config-env-php": "PHP $1 ਜੜਿਆ ਗਿਆ।",
+ "config-env-hhvm": "HHVM $1 ਜੜਿਆ ਗਿਆ।",
+ "config-db-wiki-settings": "ਇਸ ਵਿਕੀ ਦੀ ਪਛਾਣ ਕਰਾਉ",
+ "config-db-name": "ਤੱਥ-ਅਧਾਰ ਦਾ ਨਾਂ:",
"mainpagetext": "'''ਮੀਡਿਆਵਿਕਿ ਠੀਕ ਤਰ੍ਹਾਂ ਇੰਸਟਾਲ ਹੋ ਗਿਆ ਹੈ।'''"
}
diff --git a/includes/installer/i18n/pl.json b/includes/installer/i18n/pl.json
index 2e7e0233..5cf71c40 100644
--- a/includes/installer/i18n/pl.json
+++ b/includes/installer/i18n/pl.json
@@ -17,7 +17,8 @@
"WTM",
"Alan ffm",
"Matik7",
- "Pio387"
+ "Pio387",
+ "Darellur"
]
},
"config-desc": "Instalator MediaWiki",
@@ -67,7 +68,7 @@
"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-no-db": "Nie można odnaleźć właściwego sterownika bazy danych! Musisz zainstalować sterownik bazy danych dla PHP.\nMożna użyć {{PLURAL:$2|następującego typu bazy|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ć.",
@@ -88,7 +89,7 @@
"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-mod-security": "''' Ostrzeżenie ''': Serwer sieci web ma włączone [http://modsecurity.org/ mod_security]. Jeśli jest niepoprawnie skonfigurowane, może być przyczyną problemów MediaWiki lub innego oprogramowania, które pozwala użytkownikom na wysyłanie dowolnej zawartości.\nSprawdź w [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.",
diff --git a/includes/installer/i18n/pms.json b/includes/installer/i18n/pms.json
index 671671bc..36b48c8e 100644
--- a/includes/installer/i18n/pms.json
+++ b/includes/installer/i18n/pms.json
@@ -17,7 +17,7 @@
"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-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>. 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.",
@@ -44,21 +44,19 @@
"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-welcome": "=== Contròj d'ambient ===\nDle verìfiche ëd base a saran adess fàite për vëdde se st'ambient a va bin për l'instalassion ëd MediaWiki.\nCh'as visa d'anserì coste anformassion s'a sërca d'agiut su com completé l'instalassion.",
"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-env-hhvm": "HHVM $1 a l'é instalà.",
"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-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.\n{{PLURAL:$2|La sòrt ëd base ëd dàit mantnùa a l'é costa|Le sòrt ëd base ëd dàit mantùe a son coste}} sì-dapress: $1.\n\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ò anstalé, për esempi, ël mòdul <code>php5-mysql</code>.",
"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à.",
diff --git a/includes/installer/i18n/pt-br.json b/includes/installer/i18n/pt-br.json
index 68a94e83..132644da 100644
--- a/includes/installer/i18n/pt-br.json
+++ b/includes/installer/i18n/pt-br.json
@@ -12,7 +12,9 @@
"Cybermandrake",
"Fabsouza1",
"Rodrigo codignoli",
- "Tuliouel"
+ "Tuliouel",
+ "Marcos dias de oliveira",
+ "Fasouzafreitas"
]
},
"config-desc": "O instalador do MediaWiki",
@@ -57,6 +59,7 @@
"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-env-hhvm": "HHVM $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].",
@@ -217,6 +220,8 @@
"config-instantcommons": "Ativar o Instant Commons",
"config-cc-again": "Escolha novamente...",
"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-extensions": "Extensões",
"config-install-step-done": "feito",
"config-install-extensions": "Incluindo extensões",
@@ -237,6 +242,7 @@
"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-help-tooltip": "clique para expandir",
"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>",
diff --git a/includes/installer/i18n/pt.json b/includes/installer/i18n/pt.json
index 835cf9b4..b63905b8 100644
--- a/includes/installer/i18n/pt.json
+++ b/includes/installer/i18n/pt.json
@@ -64,7 +64,7 @@
"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-no-db": "Não foi possível encontrar um controlador apropriado da base de dados! Precisa de instalar um controlador da base de dados para o PHP. {{PLURAL:$2|É aceite o seguinte tipo|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.",
diff --git a/includes/installer/i18n/qqq.json b/includes/installer/i18n/qqq.json
index 772ce969..fba5165c 100644
--- a/includes/installer/i18n/qqq.json
+++ b/includes/installer/i18n/qqq.json
@@ -63,11 +63,10 @@
"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-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.\n* $2 is the count of items in $1 - for use in plural.",
"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.",
diff --git a/includes/installer/i18n/ro.json b/includes/installer/i18n/ro.json
index 77257344..fcade71f 100644
--- a/includes/installer/i18n/ro.json
+++ b/includes/installer/i18n/ro.json
@@ -4,7 +4,8 @@
"Firilacroco",
"Minisarm",
"Stelistcristi",
- "XXN"
+ "XXN",
+ "Tuxilina"
]
},
"config-desc": "Programul de instalare pentru MediaWiki",
@@ -39,9 +40,14 @@
"config-page-copying": "Copiere",
"config-page-upgradedoc": "Actualizare",
"config-page-existingwiki": "Wiki existent",
+ "config-help-restart": "Doriți să ștergeți toate datele salvate introduse și să reporniți procesul de instalare?",
"config-restart": "Da, repornește.",
+ "config-env-good": "Verificarea mediului a fost efectuată cu succes.\nPuteți instala MediaWiki.",
"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-env-hhvm": "HHVM $1 este instalat.",
+ "config-xcache": "[http://xcache.lighttpd.net/ XCache] este instalat",
+ "config-apc": "[http://www.php.net/apc APC] este instalat",
+ "config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] este instalat",
"config-db-type": "Tipul bazei de date:",
"config-db-host": "Gazdă bază de date:",
"config-db-host-oracle": "Baza de date TNS:",
@@ -65,7 +71,9 @@
"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-missing-db-name": "Trebuie să introduceți o valoare pentru „{{int:config-db-name}}”.",
+ "config-missing-db-host": "Trebuie să introduceți o valoare pentru „{{int:config-db-host}}”.",
+ "config-missing-db-server-oracle": "Trebuie să introduceți o valoare pentru „{{int:config-db-host-oracle}}”.",
"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 →",
@@ -78,6 +86,7 @@
"config-mysql-charset": "Setul de caractere al bazei de date:",
"config-mysql-binary": "Binar",
"config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Tip de autentificare:",
"config-site-name": "Numele wikiului:",
"config-site-name-blank": "Introduceți un nume pentru sit.",
"config-project-namespace": "Spațiul de nume al proiectului:",
@@ -86,7 +95,7 @@
"config-ns-other": "Altul (specificați)",
"config-ns-other-default": "MyWiki",
"config-admin-box": "Cont de administrator",
- "config-admin-name": "Numele dumneavoastră:",
+ "config-admin-name": "Numele dumneavoastră de utilizator:",
"config-admin-password": "Parolă:",
"config-admin-password-confirm": "Parola, din nou:",
"config-admin-password-blank": "Introduceți o parolă pentru contul de administrator.",
@@ -97,7 +106,7 @@
"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-wiki": "Wiki deschis",
"config-profile-no-anon": "Crearea de cont este necesară",
"config-profile-fishbowl": "Doar editorii autorizați",
"config-profile-private": "Wiki privat",
@@ -112,6 +121,7 @@
"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-settings": "Încărcare de imagini și fișiere",
"config-upload-deleted": "Director pentru fișierele șterse:",
"config-logo": "Adresa URL a siglei:",
"config-cc-again": "Alegeți din nou...",
@@ -128,6 +138,7 @@
"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-user-missing": "Utilizatorul „$1” specificat nu există.",
"config-install-tables": "Se creează tabelele",
"config-install-stats": "Se inițializează statisticile",
"config-install-keys": "Se generează cheile secrete",
@@ -135,6 +146,7 @@
"config-install-mainpage-failed": "Nu s-a putut insera pagina principală: $1",
"config-download-localsettings": "Descarcă <code>LocalSettings.php</code>",
"config-help": "ajutor",
+ "config-help-tooltip": "clic pentru a extinde",
"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
index 82203375..8b82db01 100644
--- a/includes/installer/i18n/roa-tara.json
+++ b/includes/installer/i18n/roa-tara.json
@@ -30,17 +30,29 @@
"config-page-copying": "Stoche a copie",
"config-page-upgradedoc": "Aggiornamende",
"config-page-existingwiki": "Uicchi esistende",
+ "config-restart": "Sìne, falle repartì",
+ "config-env-php": "PHP $1 ha state installate.",
+ "config-env-hhvm": "HHVM $1 ha state installate.",
"config-db-type": "Tipe de database:",
+ "config-db-host-oracle": "Database TNS:",
+ "config-db-name-oracle": "Scheme d'u 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-mysql": "MySQL (o combatibbile)",
"config-type-postgres": "PostgreSQL",
"config-type-sqlite": "SQLite",
"config-type-oracle": "Oracle",
+ "config-type-mssql": "Microsoft SQL Server",
+ "config-header-mysql": "'Mbostaziune de MySQL",
+ "config-header-postgres": "'Mbostaziune de PostgreSQL",
+ "config-header-sqlite": "'Mbostaziune de SQLite",
+ "config-header-oracle": "'Mbostaziune de Oracle",
+ "config-header-mssql": "'Mbostaziune de Microsoft SQL Server",
+ "config-invalid-db-type": "Tipe de database invalide.",
"config-mysql-innodb": "InnoDB",
"config-mysql-myisam": "MyISAM",
"config-admin-email": "Indirizze e-mail:",
@@ -51,6 +63,7 @@
"config-install-schema": "Stoche a creje 'u scheme",
"config-install-pg-schema-not-exist": "'U scheme PostgreSQL non g'esiste.",
"config-help": "ajute",
+ "config-help-tooltip": "cazze pe spannere",
"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
index b8a36bef..386215ab 100644
--- a/includes/installer/i18n/ru.json
+++ b/includes/installer/i18n/ru.json
@@ -17,7 +17,8 @@
"아라",
"Meshkov.a",
"Eroha",
- "Seb35"
+ "Seb35",
+ "Striking Blue"
]
},
"config-desc": "Инсталлятор MediaWiki",
@@ -67,7 +68,7 @@
"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-no-db": "Не удалось найти подходящие драйвера баз данных! Вам необходимо установить драйвера базы данных для PHP.\n{{PLURAL:$2|Поддерживается следующий тип|Поддерживаются следующие типы}} баз данных: $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].",
@@ -88,7 +89,7 @@
"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-mod-security": "<strong>Внимание</strong>: На вашем веб-сервере включен [http://modsecurity.org/ mod_security]/mod_security2. Многие его стандартные настройки могут вызывать проблемы для MediaWiki или другого ПО, позволяющего пользователям отправлять на сервер произвольный контент.\nОбратитесь к [http://modsecurity.org/documentation/ документации mod_security] или в службу поддержки вашего хостинг-провайдера, если вы сталкиваетесь со случайными ошибками.",
"config-diff3-bad": "GNU diff3 не найден.",
"config-git": "Найдена система контроля версий Git: <code>$1</code>.",
"config-git-bad": "Программное обеспечение по управлению версиями Git не найдено.",
@@ -192,7 +193,7 @@
"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-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": "Кодировка базы данных:",
diff --git a/includes/installer/i18n/sco.json b/includes/installer/i18n/sco.json
index 4d447351..41818934 100644
--- a/includes/installer/i18n/sco.json
+++ b/includes/installer/i18n/sco.json
@@ -48,6 +48,7 @@
"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-env-hhvm": "HHVM $1 is instawed.",
"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].",
@@ -55,6 +56,7 @@
"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-register-globals-error": "<strong>Mistak: PHP's <code>[http://php.net/register_globals register_globals]</code> optie is enablit.\nIt maun be disablit tae keep gaun wi the instawation.</strong>\nSee [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] fer help oan hou tae dae sae.",
"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.",
@@ -66,6 +68,7 @@
"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-iconv": "<strong>Fatal:</strong> PHP maun be compiled wi support fer the [http://www.php.net/manual/en/iconv.installation.php iconv 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.",
@@ -100,6 +103,7 @@
"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-username-empty": "Ye maun enter ae value fer \"{{int:config-db-username}}\".",
"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.",
@@ -269,6 +273,12 @@
"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-skins": "Skins",
+ "config-skins-help": "The skins leetit abuin were detectit in yer <code>./skins</code> directerie. Ye maun enable at least yin, n chuise the defaut.",
+ "config-skins-use-as-default": "Uise this skin aes the defaut",
+ "config-skins-missing": "Nae skins were foond; MediaWiki will uise ae fawback skin ontil ye instaw some proper skins.",
+ "config-skins-must-enable-some": "Ye need tae chuisse at least yin skin tae enable.",
+ "config-skins-must-enable-default": "The skin chosen aes the defaut maun be enablit.",
"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",
@@ -298,6 +308,8 @@
"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-updates": "Hinder the runnin o onneedit updates.",
+ "config-install-updates-failed": "<strong>Mistak:</strong> Insertin update keys intae the buirds failed wi the folleain mistak: $1",
"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.",
@@ -307,6 +319,7 @@
"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-help-tooltip": "clap tae mak muckler",
"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>",
diff --git a/includes/installer/i18n/si.json b/includes/installer/i18n/si.json
index 7dd0baad..27b7b93c 100644
--- a/includes/installer/i18n/si.json
+++ b/includes/installer/i18n/si.json
@@ -3,7 +3,8 @@
"authors": [
"Singhalawap",
"පසිඳු කාවින්ද",
- "Sahan.ssw"
+ "Sahan.ssw",
+ "Thirsty"
]
},
"config-desc": "මාධ්‍යවිකි සඳහා වූ ස්ථාපකය",
@@ -60,6 +61,7 @@
"config-missing-db-name": "\"දත්ත සංචිත නාමය\" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ",
"config-missing-db-host": "\"දත්ත සංචිත ධාරකය\" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ",
"config-missing-db-server-oracle": "\"දත්ත සංචිත TNS\" සඳහා ඔබ විසින් අගයක් දිය යුතු වේ",
+ "config-sqlite-name-help": "ඔබගේ විකිය හදුන්වාදෙන නමක් තෝරාගන්න. \nහිස්තැන් හෝ විරාම ලක්ෂණ ‍නොයොදන්න.\nමෙය SQLite දත්ත ගොනුනාමය සදහා යොදා ගනු ඇත.",
"config-regenerate": "නැවත ජනිත කරන්න LocalSettings.php →",
"config-db-web-account": "ජාල ප්‍රවේශනය සඳහා දත්ත සංචිත ගිණුම",
"config-mysql-engine": "ආචයන එන්ජිම:",
diff --git a/includes/installer/i18n/sk.json b/includes/installer/i18n/sk.json
index ad6bca7a..a22f94d6 100644
--- a/includes/installer/i18n/sk.json
+++ b/includes/installer/i18n/sk.json
@@ -37,7 +37,7 @@
"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-env-hhvm": "HHVM $1 je nainštalované.",
"config-db-type": "Typ databázy:",
"config-db-host": "Databázový server:",
"config-db-host-oracle": "Databázové TNS:",
@@ -50,9 +50,10 @@
"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-box": "Účet správcu",
"config-admin-name": "Vaše používateľské meno:",
"config-admin-password": "Heslo:",
- "config-admin-password-confirm": "Zopakuj heslo:",
+ "config-admin-password-confirm": "Zopakujte 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.",
diff --git a/includes/installer/i18n/sl.json b/includes/installer/i18n/sl.json
index b27fcdd3..d7f435ee 100644
--- a/includes/installer/i18n/sl.json
+++ b/includes/installer/i18n/sl.json
@@ -47,6 +47,7 @@
"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-env-hhvm": "HHVM $1 je nameščen.",
"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.",
@@ -54,14 +55,23 @@
"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-using-server": "Uporabljam ime strežnika \"<nowiki>$1</nowiki>\".",
+ "config-using-uri": "Uporabljam URL strežnika \"<nowiki>$1$2</nowiki>\".",
"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-install-account": "Uporabniški račun za namestitev",
"config-db-username": "Uporabniško ime zbirke podatkov:",
"config-db-password": "Geslo zbirke podatkov:",
+ "config-db-password-empty": "Prosimo, vnesite geslo za novega uporabnika podatkovne zbirke: $1. Morda je mogoče ustvarjati uporabnike brez gesel, ni pa varno.",
+ "config-db-username-empty": "Vnesti morate vrednost za \"{{int:config-db-username}}\".",
+ "config-db-install-username": "Vnesite uporabniško ime za povezavo s podatkovno zbirko med postopkom nameščanja.\nTo ni uporabniško ime računa MediaWiki, pač pa uporabniško ime za vašo podatkovno zbirko.",
+ "config-db-install-password": "Vnesite geslo za povezavo s podatkovno zbirko med postopkom nameščanja.\nTo ni geslo računa MediaWiki, pač pa geslo za vašo podatkovno zbirko.",
+ "config-db-install-help": "Vnesite uporabniško ime in geslo za povezavo s podatkovno zbirko med postopkom nameščanja.",
+ "config-db-account-lock": "Uporabite isto uporabniško ime in geslo tudi po namestitvi.",
"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",
@@ -71,12 +81,15 @@
"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-pg-test-error": "Ne morem se povezati z zbirko podatkov <strong>$1</strong>: $2",
"config-sqlite-dir": "Mapa podatkov SQLite:",
+ "config-type-mysql": "MySQL (ali združljiv)",
"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-header-mssql": "nastavitve Microsoft SQL Server",
"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}}«.",
@@ -102,6 +115,7 @@
"config-mysql-charset": "Nabor znakov zbirke podatkov:",
"config-mysql-binary": "Dvojiško",
"config-mysql-utf8": "UTF-8",
+ "config-mssql-auth": "Tip avtentikacije:",
"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.",
@@ -134,12 +148,14 @@
"config-profile-no-anon": "Zahtevano je ustvarjanje računa",
"config-profile-fishbowl": "Samo pooblaščeni urejevalci",
"config-profile-private": "Zasebni wiki",
+ "config-profile-help": "Wikiji delujejo najbolje, kadar jih lahko ureja največje možno število ljudi.\nPregled nad zadnjimi spremembami in razveljavljanje škode, ki jo povzročijo neuki ali zlonamerni uporabniki, je v MediaWiki preprosto.\n\nVendar pa je MediaWiki uporaben v celi vrsti različnih vlog, pri čemer včasih ni lahko prepričati vseh o prednostih wiki načina. Zato imate izbiro.\n\nModel <strong>{{int:config-profile-wiki}}</strong> dovoljuje urejanje vsem, tudi brez prijavljanja.\nWiki, nastavljen na <strong>{{int:config-profile-no-anon}}</strong> nudi dodatno sledljivost, vendar lahko odvrne priložnostne urejevalce.\n\nScenarij <strong>{{int:config-profile-fishbowl}}</strong> dovoljuje urejanje odobrenim uporabnikom, pri čemer sta vsebina in zgodovina strani javni.\nV načinu <strong>{{int:config-profile-private}}</strong> lahko urejajo in pregledujejo strani le odobreni uporabniki.\n\nPodrobnejše konfiguriranje uporabniških pravic je možno po namestitvi, glejte [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights vnos v uporabniškem priročniku].",
"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-gfdl": "Dovoljenje GNU za rabo proste dokumentacije 1.3 ali kasnejše",
"config-license-pd": "Javna last",
"config-license-cc-choose": "Izberite dovoljenje Creative Commons po meri",
"config-email-settings": "Nastavitve e-pošte",
diff --git a/includes/installer/i18n/sr-ec.json b/includes/installer/i18n/sr-ec.json
index 950ee44e..c63ac008 100644
--- a/includes/installer/i18n/sr-ec.json
+++ b/includes/installer/i18n/sr-ec.json
@@ -3,7 +3,8 @@
"authors": [
"Rancher",
"Михајло Анђелковић",
- "Milicevic01"
+ "Milicevic01",
+ "Aktron"
]
},
"config-desc": "Инсталација за Медијавики",
@@ -42,6 +43,8 @@
"config-db-type": "Тип базе података:",
"config-db-host": "Хост базе података",
"config-db-name": "Назив базе података:",
+ "config-db-password": "Лозинка за базу података:",
+ "config-db-password-empty": "Унесите лозинку за новог корисника базе података: ($1).\n\nМада је могуће отворити налоге без лозинки, то се не препоручује.",
"config-type-mysql": "MySQL (или компактибилан)",
"config-type-postgres": "PostgreSQL",
"config-type-sqlite": "SQLite",
@@ -49,6 +52,7 @@
"config-type-mssql": "Microsoft SQL Server",
"config-header-mysql": "MySQL подешавања",
"config-header-mssql": "Поставке Microsoft SQL Server-а",
+ "config-invalid-db-type": "Неважећи тип базе података.",
"config-mssql-old": "Потребан је Microsoft SQL Server $1 или новији. Ви имате $2.",
"config-mysql-innodb": "InnoDB",
"config-mysql-myisam": "MyISAM",
@@ -60,6 +64,10 @@
"config-admin-name": "Корисничко име:",
"config-admin-password": "Лозинка:",
"config-admin-email": "Адреса е-поште:",
+ "config-optional-skip": "Досадно ми је, хајде да инсталирамо вики.",
+ "config-profile-no-anon": "Неопходно је отворити налог",
+ "config-profile-fishbowl": "Само овлашћени корисници",
+ "config-profile-private": "Приватна вики",
"config-license": "Ауторска права и лиценца:",
"config-license-none": "Без заглавља са лиценцом",
"config-license-cc-by-sa": "Creative Commons Ауторство-Делити под истим условима (CC BY-SA)",
@@ -68,6 +76,13 @@
"config-license-cc-0": "Creative Commons Zero (јавно власништво)",
"config-license-gfdl": "ГНУ-ова лиценца за слободну документацију верзија 1.3 или новија верзија",
"config-license-pd": "Јавно власништво",
+ "config-email-settings": "Подешавања е-поште",
+ "config-cc-not-chosen": "Одаберите која Кријејтив комонс лиценца вам одговара и потврдите.",
+ "config-skins": "Теме",
+ "config-install-step-done": "готово",
+ "config-install-step-failed": "није успело",
+ "config-help": "помоћ",
+ "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/sr-el.json b/includes/installer/i18n/sr-el.json
index a0cd5f55..b1a97e9b 100644
--- a/includes/installer/i18n/sr-el.json
+++ b/includes/installer/i18n/sr-el.json
@@ -34,6 +34,7 @@
"config-type-oracle": "Oracle",
"config-site-name": "Ime vikija:",
"config-license-cc-0": "Creative Commons Zero (javno vlasništvo)",
+ "config-skins": "Teme",
"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/su.json b/includes/installer/i18n/su.json
index 7a0976b3..b8f131ee 100644
--- a/includes/installer/i18n/su.json
+++ b/includes/installer/i18n/su.json
@@ -1,5 +1,9 @@
{
- "@metadata": [],
- "mainpagetext": "'''''Software'' MediaWiki geus diinstal.'''",
+ "@metadata": {
+ "authors": [
+ "Kandar"
+ ]
+ },
+ "mainpagetext": "<strong>MediaWiki geus réngsé diinstal.</strong>",
"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
index 2bdfb100..87ac7a53 100644
--- a/includes/installer/i18n/sv.json
+++ b/includes/installer/i18n/sv.json
@@ -6,7 +6,9 @@
"WikiPhoenix",
"Josve05a",
"Lokal Profil",
- "Tobulos1"
+ "Tobulos1",
+ "Rotsee",
+ "Boom"
]
},
"config-desc": "Installationsprogrammet för MediaWiki",
@@ -56,7 +58,7 @@
"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-no-db": "Kunde inte hitta en lämplig databasdrivrutin! Du måste installera en databasdrivrutin för PHP.\nFöljande databas{{PLURAL:$2|typ |typer}} 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å.",
@@ -226,7 +228,7 @@
"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-profile-help": "Wikier 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 om 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",
diff --git a/includes/installer/i18n/te.json b/includes/installer/i18n/te.json
index 86b760c8..ba84435c 100644
--- a/includes/installer/i18n/te.json
+++ b/includes/installer/i18n/te.json
@@ -48,7 +48,6 @@
"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 అందుబాటులో ఉండదు.",
@@ -201,6 +200,7 @@
"config-memcache-badport": "Memcached పోర్టు సఖ్యలు $1, $2 ల మధ్య ఉండాలి.",
"config-extensions": "పొడిగింతలు",
"config-extensions-help": "పైన చూపిన పొడిగింతలు మీ <code>./extensions</code> డైరెక్టరీలో ఉన్నాయి.\n\nవాటికి అదనంగా కాన్ఫిగరేషన్ అవసరం కావచ్చు. అయితే మీరు వాటిని చేతనం చెయ్యవచ్చు.",
+ "config-skins": "అలంకారాలు",
"config-install-alreadydone": "<strong>హెచ్చరిక:</strong> మీరు ఈసరికే MediaWiki ని స్థాపించినట్లుగా అనిపిస్తోంది. మళ్ళీ స్థాపించే ప్రయత్నం చేస్తున్నట్లున్నారు.\nతరువాత పేజీకి వెళ్ళండి.",
"config-install-begin": "\"{{int:config-continue}}\" నొక్కి, MediaWiki స్థాపనను మొదలుపెట్టవచ్చు.\nఇంకా మార్పులు చెయ్యదలిస్తే, \"{{int:config-back}}\" నొక్కండి.",
"config-install-step-done": "పూర్తయింది",
diff --git a/includes/installer/i18n/th.json b/includes/installer/i18n/th.json
index ec6526be..fd406a78 100644
--- a/includes/installer/i18n/th.json
+++ b/includes/installer/i18n/th.json
@@ -2,9 +2,97 @@
"@metadata": {
"authors": [
"Korrawit",
- "Horus"
+ "Horus",
+ "Octahedron80"
]
},
+ "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-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-memory-raised": "<code>memory_limit</code> ของ PHP คือ $1 ได้เพิ่มเป็น $2",
+ "config-memory-bad": "<strong>คำเตือน:</strong> <code>memory_limit</code> ของ PHP คือ $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-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]/mod_security2 เปิดใช้งานอยู่ การตั้งค่าทั่วไปหลายอย่างของสิ่งนี้จะก่อให้เกิดปัญหาสำหรับมีเดียวิกิ และซอฟต์แวร์อื่นที่อนุญาตให้ผู้ใช้สามารถโพสต์เนื้อหาได้ตามใจ\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-using-server": "ใช้ชื่อเซิร์ฟเวอร์ \"<nowiki>$1</nowiki>\"",
+ "config-using-uri": "ใช้ยูอาร์แอลของเซิร์ฟเวอร์ \"<nowiki>$1$2</nowiki>\"",
+ "config-mysql-innodb": "อินโนดีบี",
+ "config-mysql-myisam": "มายไอแซม",
+ "config-mysql-binary": "ไบนารี",
+ "config-mysql-utf8": "ยูทีเอฟ-8",
+ "config-site-name": "ชื่อของวิกิ:",
+ "config-ns-generic": "โครงการ",
+ "config-ns-other-default": "วิกิของฉัน",
+ "config-admin-box": "บัญชีผู้ดูแลระบบ",
+ "config-admin-name": "ชื่อผู้ใช้ของคุณ:",
+ "config-admin-password": "รหัสผ่าน:",
+ "config-admin-password-confirm": "รหัสผ่านอีกครั้ง:",
+ "config-admin-email": "ที่อยู่อีเมล:",
+ "config-license-pd": "สาธารณสมบัติ",
+ "config-extensions": "ส่วนขยาย",
+ "config-install-step-done": "เสร็จสิ้น",
+ "config-install-step-failed": "ล้มเหลว",
+ "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> ตารางมีเดียวิกิดูเหมือนว่ามีอยู่แล้ว\nข้ามการสร้างไป",
+ "config-install-tables-failed": "<strong>ความผิดพลาด:</strong> การสร้างตารางล้มเหลวด้วยความผิดพลาดต่อไปนี้: $1",
+ "config-install-interwiki-list": "ไม่สามารถอ่านไฟล์ <code>interwiki.list</code>",
+ "config-install-sysop": "สร้างบัญชีผู้ใช้ที่เป็นผู้ดูแลระบบ",
+ "config-download-localsettings": "ดาวน์โหลด <code>LocalSettings.php</code>",
+ "config-help-tooltip": "คลิกเพื่อขยาย",
+ "config-nofile": "ไม่พบไฟล์ \"$1\" มันอาจถูกลบไปแล้วหรือไม่?",
"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/tl.json b/includes/installer/i18n/tl.json
index 9a894c25..4b835008 100644
--- a/includes/installer/i18n/tl.json
+++ b/includes/installer/i18n/tl.json
@@ -50,7 +50,6 @@
"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].",
@@ -58,7 +57,6 @@
"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.",
diff --git a/includes/installer/i18n/tr.json b/includes/installer/i18n/tr.json
index 83c51ee6..e9b05cf2 100644
--- a/includes/installer/i18n/tr.json
+++ b/includes/installer/i18n/tr.json
@@ -6,7 +6,9 @@
"Rhinestorm",
"SiLveRLeaD",
"Trncmvsr",
- "Sayginer"
+ "Sayginer",
+ "Trockya",
+ "Aşilleus"
]
},
"config-desc": "MediaWiki yükleyicisi",
@@ -64,8 +66,10 @@
"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-type": "Veritabanı tipi:",
"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-host-oracle": "Veritabanı TNS:",
"config-db-wiki-settings": "Bu wikiyi tanımla",
"config-db-name": "Veritabanı adı:",
"config-db-name-oracle": "Veritabanı şeması:",
@@ -175,6 +179,7 @@
"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-pg-no-create-privs": "Kurulum için belirttiğiniz hesap yeni bir hesap oluşturmak için gereken izinlere sahip değil.",
"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",
diff --git a/includes/installer/i18n/uk.json b/includes/installer/i18n/uk.json
index 9eee1a7f..e0304433 100644
--- a/includes/installer/i18n/uk.json
+++ b/includes/installer/i18n/uk.json
@@ -55,6 +55,7 @@
"config-env-good": "Перевірку середовища успішно завершено.\nВи можете встановити MediaWiki.",
"config-env-bad": "Було проведено перевірку середовища. Ви не можете встановити MediaWiki.",
"config-env-php": "Встановлено версію PHP: $1.",
+ "config-env-hhvm": "HHVM $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 нормалізацію Юнікоду].",
@@ -314,6 +315,7 @@
"config-install-stats": "Ініціалізація статистики",
"config-install-keys": "Генерація секретних ключів",
"config-insecure-keys": "'''Увага:''' {{PLURAL:$2|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",
diff --git a/includes/installer/i18n/vi.json b/includes/installer/i18n/vi.json
index 1f3f89ef..30342aa3 100644
--- a/includes/installer/i18n/vi.json
+++ b/includes/installer/i18n/vi.json
@@ -3,15 +3,23 @@
"authors": [
"පසිඳු කාවින්ද",
"Minh Nguyen",
- "Withoutaname"
+ "Withoutaname",
+ "Dinhxuanduyet"
]
},
"config-desc": "Trình cài đặt MediaWiki",
"config-title": "Cài đặt MediaWiki $1",
"config-information": "Thông tin",
+ "config-localsettings-upgrade": "Một tập tin <code>LocalSettings.php</code> đã được phát hiện.\nĐể nâng cấp bản cài đặt này, xin nhập giá trị của <code>$wgUpgradeKey</code> trong hộp thoại bên dưới đây.\nBạn sẽ tìm thấy nó trong <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Một tập tin <code>LocalSettings.php</code> đã được phát hiện.\nĐể nâng cấp bản cài đặt này, hãy chạy <code>update.php</code> thay thế.",
"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-upgrade-key-missing": "Một bản cài đặt MediaWiki sẵn đã được phát hiện.\nĐể nâng cấp bản cài đặt này, hãy thêm dòng sau vào cuối <code>LocalSettings.php</code>:\n\n$1",
+ "config-localsettings-incomplete": "Tập tin <code>LocalSettings.php</code> đã tồn tại hình như không hoàn chỉnh.\nBiến $1 chưa được đặt.\nXin hãy thay đổi <code>LocalSettings.php</code> để đặt biến này, rồi bấm “{{int:Config-continue}}”.",
+ "config-localsettings-connection-error": "Đã xuất hiện lỗi khi kết nối với cơ sở dữ liệu dùng cấu hình trong <code>LocalSettings.php</code>. Xin hãy sửa lại cấu hình và thử lại.\n\n$1",
"config-session-error": "Lỗi khi bắt đầu phiên làm việc: $1",
+ "config-session-expired": "Dữ liệu phiên làm việc của bạn dường như đã hết hạn. Các phiên làm việc được cấu hình để kéo dài $1. Để tăng thời gian này, đặt <code>session.gc_maxlifetime</code> trong php.ini, rồi khởi động lại quá trình cài đặt.",
+ "config-no-session": "Đã mất dữ liệu phiên làm việc của bạn! Kiểm tra tập tin php.ini và đảm bảo rằng <code>session.save_path</code> đã được đặt thành một thư mục thích hợp.",
"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:",
@@ -33,46 +41,100 @@
"config-page-copying": "Sao chép",
"config-page-upgradedoc": "Nâng cấp",
"config-page-existingwiki": "Wiki đã tồn tại",
+ "config-help-restart": "Bạn có muốn xóa tất cả dữ liệu được lưu mà bạn vừa nhập và khởi động lại quá trình cài đặt?",
"config-restart": "Có, khởi động lại nó",
+ "config-welcome": "=== Kiểm tra môi trường ===\nBây giờ sẽ kiểm tra sơ qua môi trường này có phù hợp cho việc cài đặt MediaWiki.\nHãy nhớ bao gồm thông tin này khi nào xin hỗ trợ hoàn thành việc cài đặt.",
+ "config-copyright": "=== Bản quyền và Điều khoản ===\n\n$1\n\nChương trình này là phần mềm tự do; bạn có thể phân phối lại và/hoặc chỉnh sửa nó dưới điều khoản của Giấy phép Công cộng GNU (GNU General Public License) do Quỹ Phần mềm Tự do (Free Software Foundation) xuất bản; hoặc phiên bản 2 của giấy phép đó, hoặc (tùy theo ý bạn) bất kỳ phiên bản nào sau này.\n\nChương trình này được phân phối với hy vọng rằng nó sẽ hữu ích, nhưng <strong>không có bất kỳ bảo hành nào</strong>; không có thậm chí bảo hành bao hàm về <strong>khả năng thương mại</strong> hoặc <strong>sự thích hợp với một mục đích cụ thể</strong>.\nXem Giấy phép Công cộng GNU để biết thêm chi tiết.\n\nBạn phải nhận <doclink href=Copying>một bản sao của Giấy phép Công cộng GNU</doclink> đi kèm chương trình này; nếu không, hãy viết thư cho Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, Hoa Kỳ, hoặc [http://www.gnu.org/copyleft/gpl.html đọc nó trên mạng].",
+ "config-sidebar": "* [//www.mediawiki.org/wiki/Special:MyLanguage/MediaWiki Trang chủ MediaWiki]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Hướng dẫn sử dụng]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Contents Hướng dẫn quản lý]\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Câu thường hỏi]\n----\n* <doclink href=Readme>Cần đọc trước</doclink>\n* <doclink href=ReleaseNotes>Ghi chú phát hành</doclink>\n* <doclink href=Copying>Sao chép</doclink>\n* <doclink href=UpgradeDoc>Nâng cấp</doclink>",
"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-env-hhvm": "HHVM $1 được cài đặt.",
"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-unicode-pure-php-warning": "<strong>Cảnh báo:</strong> [http://pecl.php.net/intl intl PECL extension] không được phép xử lý Unicode chuẩn hóa, trả lại thực thi PHP-gốc chậm.\nNếu bạn chạy một site lưu lượng lớn, bạn phải để ý qua một chút trên [//www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations Unicode normalization].",
+ "config-unicode-update-warning": "<strong>Cảnh báo:</strong> Phiên bản cài đặt của gói Unicode chuẩn hóa sử dụng một phiên bản cũ của thư viện [http://site.icu-project.org/ the ICU project].\nBạn phải [//www.mediawiki.org/wiki/Special:MyLanguage/nâng cấp Unicode_normalization_considerations] nếu bạn quan tâm đến việc sử dụng Unicode.",
+ "config-no-db": "Không tìm thấy một trình điều khiển cơ sở dữ liệu phù hợp! Bạn cần phải cài một trình điều khiển cơ sở dữ liệu cho PHP.\n{{PLURAL:$2|Loại|Các loại}} cơ sở dữ liệu sau đây được hỗ trợ: $1.\n\nNếu bạn đã biên dịch PHP lấy, cấu hình lại nó mà kích hoạt một trình khách cơ sở dữ liệu, ví dụ bằng lệnh <code>./configure --with-mysqli</code>.\nNếu bạn đã cài PHP từ một gói Debian hoặc Ubuntu, thì bạn cũng cần phải cài ví dụ gói <code>php5-mysql</code>.",
+ "config-outdated-sqlite": "<strong>Chú ý:</strong> Bạn có SQLite $1, phiên bản này thấp hơn phiên bản yêu câu tối thiểu $2. SQLite sẽ không có tác dụng.",
+ "config-no-fts3": "<strong>Chú ý:</strong> SQLite được biên dịch mà không có [//sqlite.org/fts3.html mô đun FTS3], nên các chức năng tìm kiếm sẽ bị vô hiệu trên hệ thống phía sau này.",
+ "config-register-globals-error": "<strong>Lỗi: Tùy chọn <code>[http://php.net/register_globals register_globals]</code> của PHP đã được kích hoạt.\nNó phải bị vô hiệu để tiếp tục quá trình cài đặt.</strong>\nXem [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] để biết cách thực hiện.",
+ "config-magic-quotes-gpc": "<strong>Lỗi chí tử: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] đang hoạt động!</strong>\nTùy chọn này sẽ làm hỏng dữ liệu nhập một cách không thể đoán trước.\nBạn không thể cài đặt hoặc sử dụng MediaWiki trừ phi tùy chọn này bị vô hiệu.",
+ "config-magic-quotes-runtime": "<strong>Lỗi chí tử: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] đang hoạt động!</strong>\nTùy chọn này sẽ làm hỏng dữ liệu nhập một cách không thể đoán trước.\nBạn không thể cài đặt hoặc sử dụng MediaWiki trừ phi tùy chọn này bị vô hiệu.",
+ "config-magic-quotes-sybase": "<strong>Lỗi chí tử: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] đang hoạt động!</strong>\nTùy chọn này sẽ làm hỏng dữ liệu nhập một cách không thể đoán trước.\nBạn không thể cài đặt hoặc sử dụng MediaWiki trừ phi tùy chọn này bị vô hiệu.",
+ "config-mbstring": "<strong>Lỗi chí tử: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] được kích hoạt!</strong>\nTùy chọn này gây lỗi và có thể làm hỏng dữ liệu một cách không thể đoán trước.\nBạn không thể cài đặt hoặc sử dụng MediaWiki trừ phi tùy chọn này bị vô hiệu.",
+ "config-safe-mode": "<strong>Cảnh báo:</strong> [http://www.php.net/features.safe-mode Chế độ an toàn] của PHP đang được kích hoạt.\nNó có thể gây vấn đề, nhất là nếu dùng các chức năng tải lên tập tin và <code>math</code>.",
+ "config-xml-bad": "Mô đun XML của PHP đang bị thiếu.\nMediaWiki yêu cầu các hàm trong mô đun này và sẽ không hoạt động trong cấu hình này.\nNếu bạn đang chạy Mandrake, hãy cài đặt gói php-xml.",
+ "config-pcre-old": "<strong>Lỗi chí tử:</strong> PCRE $1 trở lên được yêu cầu phải có.\nBản nhị phân PHP của bạn dang được liên kết với PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Thông tin bổ sung].",
+ "config-pcre-no-utf8": "<strong>Lỗi chí tử:</strong> Mô đun PCRE của PHP dường như được biên dịch mà không có hỗ trợ PCRE_UTF8.\nMediaWiki yêu cầu phải có hỗ trợ UTF-8 để hoạt động chính xác.",
+ "config-memory-raised": "<code>memory_limit</code> của PHP là $1, tăng lên $2.",
+ "config-memory-bad": "<strong>Cảnh báo:</strong> <code>memory_limit</code> của PHP là $1.\nGiá trị này có lẽ quá thấp.\nCài đặt có thể bị thất bại!",
+ "config-ctype": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/ctype.installation.php phần mở rộng Ctype].",
+ "config-iconv": "<strong>Lỗi chí tử:</strong> PHP phải được biên dịch với hỗ trợ cho [http://www.php.net/manual/en/iconv.installation.php phần mở rộng iconv].",
+ "config-json": "<strong>Lỗi chí tử:</strong> PHP được biên dịch mà không có hỗ trợ cho JSON.\nBạn phải cài đặt hoặc phần mở rộng JSON PHP hoặc phần mở rộng [http://pecl.php.net/package/jsonc PECL jsonc] trước khi cài đặt MediaWiki.\n* Phần mở rộng PHP có sẵn trong Red Hat Enterprise Linux (CentOS) 5 và 6 nhưng phải được kích hoạt trong <code>/etc/php.ini</code> hoặc <code>/etc/php.d/json.ini</code>.\n* Một số phiên bản Linux được phát hành sau tháng 5 năm 2013 bỏ qua phần mở rộng PHP và gói lại phần mở rộng PECL là <code>php5-json</code> hoặc <code>php-pecl-jsonc</code> thay thế.",
"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-no-cache": "<strong>Cảnh báo:</strong> Không tìm thấy [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] hoặc [http://www.iis.net/download/WinCacheForPhp WinCache].\nVùng nhớ đệm đối tượng không được kích hoạt.",
+ "config-mod-security": "<strong>Cảnh báo:</strong> [http://modsecurity.org/ mod_security]/mod_security2 đã được kích hoạt trên máy chủ Web của bạn. Nhiều cấu hình phổ biến của phần mềm này sẽ gây vấn đề cho MediaWiki và những phần mềm khác cho phép người dùng đăng các nội dung tùy tiện.\nNếu có thể, bạn nên vô hiệu nó. Còn không, tra cứu [http://modsecurity.org/documentation/ tài liệu mod_security] hoặc liên hệ với nhà cung cấp hỗ trợ cho máy chủ nếu bạn gặp những lỗi ngẫu nhiên nào đó.",
"config-diff3-bad": "Không tìm thấy GNU diff3.",
"config-git": "Đã tìm thấy phần mềm điều khiển phiên bản Git: <code>$1</code>.",
"config-git-bad": "Không tìm thấy phần mềm điều khiển phiên bản Git.",
+ "config-imagemagick": "Đã tìm thấy ImageMagick: <code>$1</code>.\nChức năng thu nhỏ hình ảnh sẽ được kích hoạt nếu bạn cho phép tải lên.",
+ "config-gd": "Đã tìm thấy thư viện đồ họa GD đi kèm.\nChức năng thu nhỏ hình ảnh sẽ được kích hoạt nếu bạn cho phép tải lên.",
+ "config-no-scaling": "Không thể tìm thấy thư viện GD hoặc ImageMagic. Chức năng thu nhỏ hình ảnh sẽ bị vô hiệu.",
+ "config-no-uri": "<strong>Lỗi:</strong> Không thể xác định URI hiện tại. Cài đặt bị thất bại.",
+ "config-no-cli-uri": "<strong>Cảnh báo:</strong> Không có <code>--scriptpath</code> nào được xác định, nên sử dụng mặc định: <code>$1</code>.",
"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-uploads-not-safe": "<strong>Cảnh báo:</strong> Thư mục tải lên mặc định của bạn, <code>$1</code>, đang có lỗ hỏng bảo mật, dễ bị khai thác bởi các đoạn mã thực thi xấu.\nMặc dù MediaWiki kiểm tra tất cả các tập tin tải lên để tránh nguy cơ bảo mật, chúng tôi đặc biệt khuyến cáo [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security#Upload_security đóng lỗ hỏng bảo mật này lại] trước khi kích hoạt tính năng tải lên.",
+ "config-no-cli-uploads-check": "<strong>Cảnh báo:</strong> Thư mục tải lên mặc định của bạn (<code>$1</code>) không được kiểm tra lỗ hỏng bảo mật dễ bị khai thác bởi các đoạn mã thực thi xấu trong quá trình cài đặt giao diện dòng lệnh.",
+ "config-brokenlibxml": "Hệ thống của bạn có kết hợp các phiên bản lỗi lầm của PHP và libxml2, điều này có thể gây ra tổn thương không nhìn thấy được đối với dữ liệu trong MediaWiki và các ứng dụng Web khác.\nHãy nâng cấp lên phiên bản libxml2 2.7.3 trở lên ([https://bugs.php.net/bug.php?id=45996 lỗi nộp PHP]).\nCài đặt bị hủy bỏ.",
+ "config-suhosin-max-value-length": "Suhosin được cài đặt và hạn chế tham số GET <code>length</code> (độ dài) không thể vượt quá $1 byte.\nThành phần ResourceLoader của MediaWiki sẽ khắc phục giới hạn này, nhưng điều này sẽ làm giảm hiệu suất.\nNếu có thể, bạn nên tăng <code>suhosin.get.max_value_length</code> lên 1024 trở lên trong <code>php.ini</code>, và đặt <code>$wgResourceLoaderMaxQueryLength</code> cùng giá trị trong <code>LocalSettings.php</code>.",
"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-help": "Nếu máy chủ cơ sở dữ liệu của bạn nằm trên máy chủ khác, hãy điền tên hoặc địa chỉ IP của máy chủ vào đây.\n\nNếu bạn đang dùng Web hosting chia sẻ, tài liệu của nhà cung cấp hosting của bạn sẽ có tên chính xác của máy chủ.\n\nNếu bạn đang cài đặt trên một máy chủ Windows và sử dụng MySQL, việc dùng “localhost” có thể không hợp với tên máy chủ. Nếu bị như vậy, hãy thử “127.0.0.1” tức địa chỉ IP địa phương.\n\nNếu bạn đang dùng PostgreSQL, hãy để trống mục này để kết nối với một ổ cắm Unix.",
"config-db-host-oracle": "TNS cơ sở dữ liệu:",
+ "config-db-host-oracle-help": "Nhập một [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Tên Kết nối Địa phương] hợp lệ; một tập tin tnsnames.ora phải được hiển thị đối với cài đặt này.<br />Nếu bạn đang sử dụng các thư viện trình khách 10g trở lên, bạn cũng có thể sử dụng phương pháp đặt tên [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].",
"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-help": "Chọn một tên để chỉ thị wiki của bạn.\nKhông nên đưa dấu cách vào tên này.\n\nNếu bạn đang sử dụng Web hosting chia sẻ, nhà cung cấp hosting của bạn hoặc là sẽ cung cấp cho bạn một tên cơ sở dữ liệu cụ thể để sử dụng hoặc là sẽ cho phép bạn tạo ra các cơ sở dữ liệu thông qua một bảng điều khiển.",
"config-db-name-oracle": "Giản đồ cơ sở dữ liệu:",
+ "config-db-account-oracle-warn": "Có ba trường hợp được hỗ trợ để cài đặt Oracle làm cơ sở dữ liệu phía sau:\n\nNếu bạn muốn tạo tài khoản cơ sở dữ liệu trong quá trình cài đặt, xin vui lòng cung cấp một tài khoản với vai trò SYSDBA là tài khoản cơ sở dữ liệu để cài đặt và xác định định danh mong muốn cho tài khoản truy cập Web, nếu không bạn có thể tạo tài khoản truy cập Web thủ công và chỉ cung cấp tài khoản đó (nếu nó có các quyền yêu cầu để tạo ra các đối tượng giản đồ) hoặc cung cấp hai tài khoản riêng, một có quyền tạo ra và một bị hạn chế có quyền truy cập Web.\n\nMột kịch bản để tạo một tài khoản với quyền yêu cầu có sẵn trong thư mục cài đặt “maintenance/oracle/”. Hãy nhớ rằng việc sử dụng một tài khoản bị hạn chế sẽ vô hiệu hóa tất cả các khả năng bảo trì với tài khoản mặc định.",
"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-password-empty": "Xin nhập vào một mật khẩu cho người dùng cơ sở dữ liệu mới: $1. Tuy bạn có thể tạo một tài khoản người dùng mà không cần mật khẩu, nhưng khi đó sẽ không đảm bảo tính bảo mật cho bạn.",
"config-db-username-empty": "Bạn phải nhập một giá trị cho “{{int:config-db-username}}”",
+ "config-db-install-username": "Nhập tên người dùng để kết nối với cơ sở dữ liệu trong quá trình cài đặt.\nĐây không phải là tên người dùng của tài khoản MediaWiki; đây là tên người dùng cho cơ sở dữ liệu của bạn.",
+ "config-db-install-password": "Nhập mật khẩu để kết nối với cơ sở dữ liệu trong quá trình cài đặt.\nĐây không phải là mật khẩu của tài khoản MediaWiki; đây là mật khẩu cho cơ sở dữ liệu của bạn.",
+ "config-db-install-help": "Nhập tên người dùng và mật khẩu sẽ được sử dụng để kết nối với cơ sở dữ liệu trong quá trình cài đặt.",
+ "config-db-account-lock": "Sử dụng cùng tên người dùng và mật khẩu trong quá trình hoạt động bình thường",
"config-db-wiki-account": "Tài khoản người dùng để hoạt động bình thường",
+ "config-db-wiki-help": "Nhập tên người dùng và mật khẩu sẽ được sử dụng để kết nối với cơ sở dữ liệu trong quá trình hoạt động bình thường của wiki.\nNếu tài khoản không tồn tại và tài khoản cài đặt có đủ quyền hạn, tài khoản người dùng này sẽ được tạo ra với những đặc quyền tối thiểu cần thiết để vận hành wiki.",
"config-db-prefix": "Tiền tố bảng cơ sở dữ liệu:",
+ "config-db-prefix-help": "Nếu bạn cần phải chia sẻ một cơ sở dữ liệu chung với nhiều wiki, hay giữa MediaWiki và một ứng dụng Web, bạn có thể quyết định thêm một tiền tố cho tất cả các tên bảng để tránh xung đột.\nKhông sử dụng dấu cách.\n\nTrường này thường được bỏ trống.",
"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-charset-help": "<strong>Cảnh báo:</strong> Nếu bạn sử dụng <strong>UTF-8 tương thích ngược</strong> trên MySQL 4.1+ và sau đó sao lưu cơ sở dữ liệu với <code>mysqldump</code>, việc này có thể phá hủy tất cả các ký tự không phải ASCII, không thể phục hồi sao lưu bị hỏng của bạn!\n\nTrong <strong>chế độ nhị phân</strong>, MediaWiki lưu trữ văn bản UTF-8 vào cơ sở dữ liệu trong các trường nhị phân.\nĐiều này hiệu quả hơn so với chế độ UTF-8 của MySQL và cho phép bạn sử dụng đầy đủ các ký tự của Unicode.\nTrong <strong>chế độ UTF-8</strong>, MySQL sẽ biết bảng mã của dữ liệu và có thể trình bày và chuyển đổi nó chính xác,\nnhưng nó sẽ không cho phép lưu các ký tự nằm cao hơn [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Mặt phẳng đa ngôn ngữ căn bản].",
"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-db-schema-help": "Giản đồ này thường làm việc tốt.\nChỉ thay đổi nó nếu bạn biết cần phải làm như vậy.",
"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-sqlite-dir-help": "SQLite lưu tất cả các dữ liệu trong một tập tin duy nhất.\n\nThư mục mà bạn cung cấp phải cho phép máy chủ Web ghi vào khi cài đặt.\n\n<strong>Không</strong> nên làm cho nó truy cập được qua Web; đây là lý do chúng tôi không đặt nó vào cùng thư mục với các tập tin PHP của bạn.\n\nTrình cài đặt sẽ ghi một tập tin <code>.htaccess</code> đi kèm, nhưng nếu thất bại người nào đó có thể truy cập vào cơ sở dữ liệu thô của bạn.\nĐiều đó bao gồm dữ liệu người dùng thô (địa chỉ thư điện tử, mật khẩu được băm) cũng như các phiên bản bị xóa và dữ liệu bị hạn chế khác trên wiki.\n\nXem xét đặt cơ sở dữ liệu tại nơi nào khác hẳn, ví dụ trong <code>/var/lib/mediawiki/wiki_cua_ban</code>.",
"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-support-info": "MediaWiki hỗ trợ các hệ thống cơ sở dữ liệu sau đây:\n\n$1\n\nNếu bạn không thấy hệ thống cơ sở dữ liệu mà bạn đang muốn sử dụng được liệt kê dưới đây, thì hãy theo chỉ dẫn được liên kết ở trên để kích hoạt tính năng hỗ trợ.",
+ "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] là mục tiêu chính cho MediaWiki và được hỗ trợ tốt nhất. MediaWiki cũng làm việc với [{{int:version-db-mariadb-url}} MariaDB] và [{{int:version-db-percona-url}} Percona Server], là những cơ sở dữ liệu tương thích với MySQL. ([http://www.php.net/manual/en/mysqli.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của MySQL])",
+ "config-dbsupport-postgres": "* [{{int:version-db-postgres-url}} PostgreSQL] là một hệ thống cơ sở dữ liệu mã nguồn mở phổ biến như là một thay thế cho MySQL. Có thể có một số lỗi nhỏ lâu đời, và nó không được khuyến cáo sử dụng trong môi trường sản xuất. ([http://www.php.net/manual/en/pgsql.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của PostgreSQL])",
+ "config-dbsupport-sqlite": "* [{{int:version-db-sqlite-url}} SQLite] là một hệ thống cơ sở dữ liệu dung lượng nhẹ được hỗ trợ rất tốt. ([http://www.php.net/manual/en/pdo.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của SQLite], sử dụng PDO)",
+ "config-dbsupport-oracle": "* [{{int:version-db-oracle-url}} Oracle] là một cơ sở dữ liệu doanh nghiệp thương mại. ([http://www.php.net/manual/en/oci8.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của OCI8])",
+ "config-dbsupport-mssql": "* [{{int:version-db-mssql-url}} Microsoft SQL Server] là một cơ sở dữ liệu doanh nghiệp thương mại cho Windows. ([http://www.php.net/manual/en/sqlsrv.installation.php Làm thế nào để biên dịch PHP với sự hỗ trợ của SQLSRV])",
"config-header-mysql": "Thiết lập MySQL",
"config-header-postgres": "Thiết lập PostgreSQL",
"config-header-sqlite": "Thiết lập SQLite",
@@ -82,50 +144,87 @@
"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-db-server-oracle": "Cơ sở dữ liệu TNS không hợp lệ “$1”.\nHoặc sử dụng “TNS Name” hoặc một chuỗi “Easy Connect” ([http://docs.oracle.com/cd/E11882_01/network.112/e10836/naming.htm Phương pháp đặt tên Oracle]).",
+ "config-invalid-db-name": "Tên cơ sở dữ liệu không hợp lệ “$1”.\nChỉ sử dụng các chữ cái ASCII (a–z, A–Z), số (0–9), dấu gạch dưới (_) và dấu gạch ngang (-).",
+ "config-invalid-db-prefix": "Tiền tố cơ sở dữ liệu không hợp lệ “$1”.\nChỉ sử dụng các chữ cái ASCII (a–z, A–Z), số (0–9), dấu gạch dưới (_) và dấu gạch ngang (-).",
+ "config-connection-error": "$1.\n\nKiểm tra máy chủ, tên người dùng, và mật khẩu và thử lại lần nữa.",
"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-db-sys-create-oracle": "Trình cài đặt chỉ hỗ trợ sử dụng một tài khoản SYSDBA để tạo một tài khoản mới.",
+ "config-db-sys-user-exists-oracle": "Tài khoản người dùng “$1” đã tồn tại. SYSDBA chỉ có thể được sử dụng để tạo một tài khoản mớ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-name-help": "Chọn một tên để chỉ thị wiki của bạn.\nKhông sử dụng các dấu cách ( ) hoặc dấu gạch nối (-).\nTên này sẽ được sử dụng cho tên tập tin dữ liệu SQLite.",
+ "config-sqlite-parent-unwritable-group": "Không thể tạo ra thư mục dữ liệu <code><nowiki>$1</nowiki></code>, bởi vì thư mục cha <code><nowiki>$2</nowiki></code> không cho phép máy chủ Web ghi vào.\n\nTrình cài đặt đã xác định người dùng mà máy chủ Web của bạn đang chạy.\n\nHãy thiết lập để thư mục <code><nowiki>$3</nowiki></code> có thể ghi được bởi nó để tiếp tục.\nTrong một hệ thống Unix/Linux làm theo như sau:\n\n<pre>cd $2\nmkdir $3\nchgrp $4 $3\nchmod g+w $3</pre>",
+ "config-sqlite-parent-unwritable-nogroup": "Không thể tạo ra thư mục dữ liệu <code><nowiki>$1</nowiki></code>, bởi vì thư mục cha <code><nowiki>$2</nowiki></code> không cho phép máy chủ Web ghi vào.\n\nTrình cài đặt không thể xác định người sử dụng mà máy chủ web của bạn đang chạy.\nThiết lập thư mục <code><nowiki>$3</nowiki></code> có thể ghi toàn cục bởi nó (và những người khác!) để tiếp tục.\nTrong một hệ thống Unix/Linux hãy đánh các dòng lệnh sau:\n\n<pre>cd $2\nmkdir $3\nchmod a+w $3</pre>",
+ "config-sqlite-mkdir-error": "Lỗi tạo thư mục dữ liệu “$1”.\nKiểm tra vị trí lưu và thử lại.",
+ "config-sqlite-dir-unwritable": "Không thể ghi vào thư mục “$1”.\nThay đổi quyền hạn của nó để máy chủ Web có thể ghi vào, và thử lại.",
+ "config-sqlite-connection-error": "$1.\n\nKiểm tra thư mục dữ liệu và tên cơ sở dữ liệu dưới đây và thử lại.",
"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-upgrade-done": "Nâng cấp đã hoàn thành.\n\nBạn có thể [$1 bắt đầu sử dụng wiki của bạn] ngay bây giờ.\n\nNếu bạn muốn tạo lại tập tin <code>LocalSettings.php</code> của bạn, bấm nút bên dưới.\nĐiều này <strong>không được khuyến khích</strong>, trừ khi bạn đang gặp vấn đề với wiki của bạn.",
+ "config-upgrade-done-no-regenerate": "Nâng cấp đã hoàn thành.\n\nBạn có thể [$1 bắt đầu sử dụng wiki của bạn] ngay bây giờ.",
"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-unknown-collation": "<strong>Cảnh báo:</strong> Database đang sử dụng đối chiếu không được thừa nhận.",
"config-db-web-account": "Tài khoản cơ sở dữ liệu để truy cập Web",
+ "config-db-web-help": "Chọn tên người dùng và mật khẩu mà máy chủ Web sẽ sử dụng để kết nối đến máy chủ cơ sở dữ liệu trong quá trình hoạt động bình thường của wiki.",
"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-db-web-no-create-privs": "Tài khoản mà bạn xác định để cài đặt không có đủ quyền để tạo một tài khoản. Tài khoản mà bạn chỉ ra ở đây phải thực sự tồn tại trước đó.",
"config-mysql-engine": "Máy lưu trữ:",
"config-mysql-innodb": "InnoDB",
"config-mysql-myisam": "MyISAM",
+ "config-mysql-myisam-dep": "<strong>Cảnh báo:</strong> Bạn đã chọn MyISAM làm động cơ lưu trữ cho MySQL, điều này không được khuyến khích sử dụng với MediaWiki, bởi vì:\n* Nó ít hỗ trợ đồng thời do việc khóa bảng\n* Nó dễ bị lỗi hơn so với các động cơ khác\n* Kho mã nguồn của MediaWiki không phải khi nào cũng xử lý MyISAM như mong muốn\n\nNếu cài đặt MySQL của bạn hỗ trợ InnoDB, đặc biệt khuyến cáo bạn nên chọn để thay thế.\nNếu cài đặt MySQL của bạn không hỗ trợ InnoDB, có lẽ đã đến lúc để nâng cấp.",
+ "config-mysql-only-myisam-dep": "<strong>Cảnh báo:</strong> MyISAM chỉ là công cụ lưu trữ có sẵn cho MySQL trên máy tính này, và điều này không được khuyến khích sử dụng với MediaWiki, bởi vì:\n* Nó ít hỗ trợ đồng thời do việc khóa khóa\n* Nó là dễ bị hư hỏng hơn các engine khác\n* Codebase MediaWiki không phải khi nào cũng xử lý MyISAM như mong muốn\n\nCài đặt MySQL của bạn không hỗ trợ InnoDB, có lẽ đã đến lúc để nâng cấp.",
+ "config-mysql-engine-help": "<strong>InnoDB</strong> hầu như luôn là tùy chọn tốt nhất, vì nó có hỗ trợ đồng thời rất tốt.\n\n<strong>MyISAM</strong> có thể nhanh hơn trong chế độ một người dùng hoặc các cài đặt chỉ-đọc (read-only).\nCơ sở dữ liệu MyISAM có xu hướng thường xuyên bị hỏng hóc hơn so với cơ sở dữ liệu InnoDB.",
"config-mysql-charset": "Bảng mã cơ sở dữ liệu:",
"config-mysql-binary": "Nhị phân",
"config-mysql-utf8": "UTF-8",
+ "config-mysql-charset-help": "Trong <strong>chế độ nhị phân</strong>, MediaWiki lưu văn bản UTF-8 vào cơ sở dữ liệu trong các trường nhị phân.\nĐiều này hiệu quả hơn so với chế độ UTF-8 của MySQL, và cho phép bạn sử dụng đầy đủ các ký tự Unicode.\n\nTrong <strong>chế độ UTF-8 </strong>, MySQL sẽ biết những ký tự nào thiết lập dữ liệu của bạn, và có thể trình bày và chuyển đổi nó một cách thích hợp, nhưng nó sẽ không cho phép bạn lưu trữ các ký tự nằm trên [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
"config-mssql-auth": "Kiểu xác thực:",
+ "config-mssql-install-auth": "Chọn loại xác thực sẽ được sử dụng để kết nối với cơ sở dữ liệu trong quá trình cài đặt.\nNếu bạn chọn \"{{int:config-mssql-windowsauth}}\", thông tin của bất cứ người sử dụng nào mà máy chủ web đang chạy sẽ được sử dụng.",
+ "config-mssql-web-auth": "Chọn kiểu xác thực mà máy chủ web sẽ sử dụng để kết nối đến máy chủ cơ sở dữ liệu, trong quá trình hoạt động bình thường của wiki.\nNếu bạn chọn \"{{int:config-mssql-windowsauth}}\", thông tin của bất cứ người sử dụng nào mà máy chủ web đang hoạt động sẽ được sử dụng.",
"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-help": "Điều này sẽ xuất hiện trên thanh tiêu đề của trình duyệt và ở những nơi khác.",
"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-project-namespace-help": "Ví dụ sau đây của Wikipedia, nhiều wiki tách các trang sách họ với các trang nội dung, trong một \"''' không gian tên dự án'''\".\nTất cả các tiêu đề trang trong không gian tên này bắt đầu với một tiền tố nhất định, bạn có thể xác định ở đây.\nThông thường, tiền tố này được bắt nguồn từ tên của wiki, nhưng nó không thể chứa các ký tự đặc biệt như \"#\" hoặc \":\".",
+ "config-ns-invalid": "Không gian tên cụ thể \"<nowiki>$1</nowiki>\" không hợp lệ.\nXác định một không gian tên dự án khác.",
+ "config-ns-conflict": "Không gian tên cụ thể \"<nowiki>$1</nowiki>\" xung đột với một không gian tên MediaWiki mặc định.\nXác định một không gian tên dự án khác.",
"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-help": "Nhập tên người dùng ưa thích ở đây, ví dụ như \" Joe Bloggs\" .\nĐây là tên mà bạn sẽ sử dụng để đăng nhập vào wiki.",
"config-admin-name-blank": "Nhập tên người dùng của bảo quản viên.",
+ "config-admin-name-invalid": "Tên người dùng cụ thể \"<nowiki>$1</nowiki>\" không hợp lệ.\nChỉ định một tên người dùng khác.",
"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-email-help": "Nhập một địa chỉ email vào đây để cho phép bạn nhận được email từ những người dùng khác trên wiki, thiết lập lại mật khẩu của bạn, và sẽ được thông báo về những thay đổi trong các trang nằm trong danh sách theo dõi của bạn. Bạn có thể để trống trường này.",
+ "config-admin-error-user": "Lỗi nội bộ khi tạo một admin với tên <nowiki>$1</nowiki>\".",
+ "config-admin-error-password": "Lỗi nội bộ khi thiết lập một mật khẩu cho admin \" <nowiki>$1</nowiki>\": <pre>$2</pre>",
"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-subscribe-help": "thông báo an ninh.\nBạn nên đồng ý với nó và cập nhật bản cài đặt MediaWiki của bạn khi phiên bản mới xuất hiện.",
+ "config-subscribe-noemail": "Bạn đã cố gắng để đăng ký vào danh sách nhận thư thông báo phát hành mà không cung cấp một địa chỉ email nào cả.\nVui lòng cung cấp một địa chỉ email nếu bạn muốn đăng ký vào danh sách nhận thư.",
+ "config-almost-done": "Bạn gần như đã hoàn tất!\nBây giờ bạn có thể bỏ qua cấu hình còn lại và cài đặt wiki ngay bây giờ.",
"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-fishbowl": "Chỉ những người dùng được phép",
"config-profile-private": "Wiki riêng tư",
+ "config-profile-help": "Wiki làm việc tốt nhất khi có càng nhiều người chỉnh sửa chúng nhất có thể.\nTrong MediaWiki, rất dễ dàng để xem lại các thay đổi gần đây, và quay trở lại tình trạng ban đầu trước bất kỳ thiệt hại nào được thực hiện bởi người dùng vô tình hoặc người dùng có dụng ý xấu.\n\nTuy nhiên, nhiều người thấy là MediaWiki rất hữu ích trong chừng mực nào đó, và đôi khi thật không phải dễ dàng để thuyết phục mọi người về những lợi ích theo cách thức mà wiki mang lại.\nVì vậy, bạn có sự lựa chọn của riêng bạn.\n\nMô hình <strong>{{int:config-profile-wiki}}</strong> cho phép bất cứ ai tham gia chỉnh sửa, thậm chí không cần đăng nhập.\nMột wiki với <strong>{{int:config-profile-no-anon}}</strong> cung cấp thêm trách nhiệm, nhưng có thể ngăn chặn những người đóng góp thông thường.\n\nKịch bản <strong>{{int:config-profile-fishbowl}}</strong> cho phép người dùng được duyệt chỉnh sửa, nhưng công chúng có thể xem các trang web, bao gồm cả lịch sử.\nMột <strong>{{int:config-profile-private}}</strong> chỉ cho phép được duyệt xem các trang, với cùng nhóm được phép chỉnh sửa.\n\nNhiều cấu hình quyền sử dụng phức tạp có sẵn sau khi cài đặt, xin xem [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:User_rights relevant manual entry].",
"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ự",
@@ -135,29 +234,54 @@
"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-license-help": "Nhiều wiki công khai phát hành tất cả các đóng góp theo một [http://freedomdefined.org/Definition/Vi?uselang=vi giấy phép tự do].\nĐiều này giúp tạo nên thái độ cộng đồng sở hữu và ủng hộ sự đóng góp lâu dài.\nNói chung, một wiki riêng tư hoặc của công ty không nhất thiết phải sử dụng một giấy phép tự do.\n\nNếu bạn muốn được phép sử dụng văn bản từ Wikipedia và muốn Wikipedia nhận được những văn bản được sao chép từ wiki của bạn, bạn nên chọn <strong>{{int:config-license-cc-by-sa}}</strong>.\n\nWikipedia từng sử dụng Giấy phép Tài liệu Tự do GNU.\nGFDL là một giấy phép hợp lệ nhưng khó hiểu trên thực tế.\nNội dung được phát hành theo GFDL cũng khó tái sử dụng.",
"config-email-settings": "Thiết lập thư điện tử",
"config-enable-email": "Cho phép gửi thư điện tử đi",
+ "config-enable-email-help": "Nếu bạn muốn email để làm việc, [http://www.php.net/manual/en/mail.configuration.php thiết lập mail của PHP] cần phải được cấu hình đúng.\nNếu bạn không muốn sử dụng bất kỳ tính năng email nào, bạn có thể vô hiệu chúng ở đây.",
"config-email-user": "Cho phép người dùng gửi thư điện tử cho người dùng khác",
+ "config-email-user-help": "Cho phép tất cả người dùng gửi email cho nhau, nếu họ đã kích hoạt nó trong cài đặt tùy chọn của họ.",
"config-email-usertalk": "Gửi thư thông báo về tin nhắn mới",
+ "config-email-usertalk-help": "Cho phép người dùng nhận được thông báo về các thay đổi trong trang thảo luận người dùng, nếu họ đã kích hoạt nó trong cài đặt tùy chọn của họ.",
"config-email-watchlist": "Gửi thư thông báo về bài theo dõi",
+ "config-email-watchlist-help": "Cho phép người dùng nhận được thông báo về các trang theo dõi của họ nếu họ đã kích hoạt nó trong ưu tiên của họ.",
"config-email-auth": "Xác minh qua thư điện tử",
+ "config-email-auth-help": "Nếu tùy chọn này được kích hoạt, người dùng phải xác nhận địa chỉ email của họ bằng cách sử dụng một liên kết được gửi tới cho họ bất cứ khi nào họ thiết lập hoặc thay đổi nó.\nChỉ có địa chỉ email được xác thực mới có thể nhận email từ những người dùng khác hoặc các email thông báo thay đổi.\nThiết lập tùy chọn này <strong>khuyến cáo sử dụng</strong> cho các wiki công cộng do khả năng các tính năng email dễ bị lạm dụng để gây hại.",
"config-email-sender": "Địa chỉ thư điện tử trả lại:",
+ "config-email-sender-help": "Nhập địa chỉ email để làm địa chỉ trở về trong mail gửi đi.\nĐây là nơi mà thư từ chối sẽ được gửi đi.\nNhiều máy chủ mail yêu cầu phải có ít nhất là phần tên miền để đảm bảo tính hợp lệ.",
"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-help": "Tập tin tải lên có khả năng làm lộ các nguy cơ bảo mật của máy chủ của bạn.\nĐể biết thêm thông tin, xin đọc [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Security phần bảo mật] trong tài liệu hướng dẫn.\n\nĐể kích hoạt tính năng tải tập tin lên, thay đổi chế độ trên thư mục con <code>hình ảnh</code> trong thư mục gốc (root) của MediaWiki để máy chủ web có thể lưu dữ liệu vào đó.\nSau đó kích hoạt tùy chọn này.",
"config-upload-deleted": "Thư mục chứa các tập tin đã xóa:",
+ "config-upload-deleted-help": "Chọn một thư mục trong đó lưu trữ các tập tin đã bị xóa.\nLý tưởng nhất, thư mục này không nên được truy cập từ trang web.",
"config-logo": "URL biểu trưng:",
+ "config-logo-help": "Giao diện mặc định của MediaWiki bao gồm không gian cho một logo 135x160 điểm ảnh trên menu sidebar (thanh bên).\nTải lên một hình ảnh kích thước thích hợp, và nhập URL hình ảnh đó vào đây.\n\nBạn có thể sử dụng <code>$wgStylePath</code> hoặc <code>$wgScriptPath</code> nếu logo của bạn liên quan đường những đường dẫn ở đây.\n\nNếu bạn không muốn có một logo, hãy bỏ trống ô này.",
"config-instantcommons": "Kích hoạt Instant Commons",
+ "config-instantcommons-help": "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] là một tính năng cho phép wiki sử dụng hình ảnh, âm thanh và tập tin đa phương tiện khác được tìm thấy trong trang web [//commons.wikimedia.org/ Wikimedia Commons].\nĐể làm được điều này, MediaWiki yêu cầu phải truy cập vào Internet.\n\nĐể biết thêm thông tin về tính năng này, trong đó có hướng dẫn về cách thiết lập cho các wiki khác với Wikimedia Commons, tham khảo thêm tại [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgForeignFileRepos tài liệu hướng dẫn].",
+ "config-cc-error": "Người chọn giấy phép Creative Commons đã không đưa ra kết quả nào.\nNhập tên giấy phép bằng tay.",
"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-help": "Lưu vào bộ nhớ đệm đối tượng được sử dụng để cải thiện tốc độ của MediaWiki bằng cách lưu vào bộ nhớ đệm những dữ liệu thường xuyên sử dụng.\nCác trang web từ trung bình cho đến các trang web lớn rất được khuyến khích kích hoạt tính năng này, và các trang web nhỏ cũng sẽ nhìn thấy lợi ích tương tự.",
+ "config-cache-none": "Không lưu vào bộ nhớ đệm (không có chức năng nhiệm vụ sẽ được loại bỏ, nhưng tốc độ có thể bị ảnh hưởng trên các trang web wiki lớn hơn)",
"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-memcached-help": "Danh sách các địa chỉ IP để sử dụng cho Memcached .\nNên xác định trên một dòng và chỉ định các cổng được sử dụng. Ví dụ:\n 127.0.0.1:11211\n 192.168.1.25:1234",
"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-noport": "Bạn không thể chỉ định một cổng để sử dụng cho máy chủ Memcached:$1.\nNếu bạn không biết cổng nào, mặc định là 11211.",
"config-memcache-badport": "Số cổng Memcached phải từ $1 đến $2.",
"config-extensions": "Phần mở rộng",
+ "config-extensions-help": "Mở rộng được liệt kê ở trên đã được phát hiện trong thư mục <code>./extensions</code> của bạn.\n\nChúng có thể yêu cầu thêm cấu hình, nhưng bạn có thể kích hoạt chúng ngay bây giờ.",
+ "config-skins": "Giao diện",
+ "config-skins-help": "Các giao diện được liệt kê ở trên đã được phát hiện trong thư mục <code>./skins</code> của bạn. Bạn phải kích hoạt ít nhất một giao diện, và chọn nó làm mặc định.",
+ "config-skins-use-as-default": "Dùng giao diện này làm mặc định",
+ "config-skins-missing": "Không giao diện nào được tìm thấy; MediaWiki sẽ sử dụng một giao diện dự phòng cho đến khi bạn cài đặt giao diện thích hợp.",
+ "config-skins-must-enable-some": "Phải chọn ít nhất một giao diện để kích hoạt.",
+ "config-skins-must-enable-default": "Giao diện được chọn làm mặc định phải được kích hoạt.",
+ "config-install-alreadydone": "<strong>Cảnh báo:</strong> Bạn dường như đã cài đặt MediaWiki và đang cố gắng để cài đặt nó lại một lần nữa.\nXin hãy chuyển sang trang tiếp theo.",
+ "config-install-begin": "Bằng cách nhấn \"{{int:config-continue}}\", bạn sẽ bắt đầu cài đặt MediaWiki của mình.\nNếu bạn vẫn muốn thay đổi, nhấn \"{{int:config-back}}\".",
"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",
@@ -165,8 +289,11 @@
"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-commit": "Đang gửi các thay đổi",
"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-pg-no-create-privs": "Tài khoản bạn xác định để cài đặt không đủ quyền hạn để tạo một tài khoản.",
+ "config-pg-not-in-role": "Tài khoản bạn xác định cho người dùng web đã tồn tại.\nTài khoản bạn xác định cho việc cài đặt không phải là một superuser(người dùng cao cấp) và không phải là một thành viên của vai trò người sử dụng web, vì vậy nó không thể tạo ra các đối tượng thuộc sở hữu của người sử dụng web.\n\nMediaWiki hiện nay yêu cầu rằng các bảng được sở hữu bởi người sử dụng web. Hãy xác định một tên tài khoản web, hoặc click \"back\" (quay trở về) và chỉ định một người dùng cài đặt có đặc quyền thích hợp.",
"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",
@@ -181,15 +308,21 @@
"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-insecure-keys": "<strong>Cảnh báo:</strong>{{PLURAL:$2|Một khóa an toàn|Khóa an toàn}} ($1) được tạo ra trong quá trình cài đặt {{PLURAL:$2|là}} không hoàn toàn an toàn. Hãy cân nhắc việc thay đổi {{PLURAL: $2|nó|chúng}} bằng tay.",
+ "config-install-updates": "Tránh các cập nhật không cần thiết",
+ "config-install-updates-failed": "<strong>Lỗi:</strong> Chèn phím cập nhật vào các bảng không thành công với các lỗi sau:1$",
"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-install-done": "<strong>Xin chúc mừng!</strong>\nBạn đã cài đặt thành công MediaWiki.\n\nBộ cài đặt đã tạo ra một file <code>LocalSettings.php</code>.\nFile này chứa tất cả các cấu hình của bạn.\n\nBạn sẽ cần phải tải nó về và đặt nó trong thư mục cài đặt wiki của bạn (cùng thư mục với index.php). Việc tải về có lẽ sẽ được khởi động tự động.\n\nNếu việc tải về không được cung cấp, hoặc nếu bạn hủy bỏ nó, bạn có thể khởi động lại tải về bằng cách nhấn vào liên kết dưới đây:\n\n$3\n\n<strong>Lưu ý:</strong> Nếu bạn không làm điều này ngay bây giờ, điều này sẽ tạo ra tập tin cấu hình sẽ không có giá trị cho bạn sau này nếu bạn thoát khỏi trình cài đặt mà không tải nó về.\n\nKhi đã việc tải về đã hoàn thành, bạn có thể <strong>[$2 truy cập trang wiki của bạn]</strong>.",
"config-download-localsettings": "Tải về <code>LocalSettings.php</code>",
"config-help": "Trợ giúp",
+ "config-help-tooltip": "nhấn chuột để mở rộng",
"config-nofile": "Không tìm thấy tập tin “$1”. Nó có phải bị xóa không?",
+ "config-extension-link": "Bạn có biết rằng wiki của bạn có hỗ trợ [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions mở rộng]?\n\nBạn có thể truy cập [//www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category phần mở rộng theo thể loại] hoặc [//www.mediawiki.org/wiki/Extension_Matrix Ma trận Mở rộng] để xem danh sách đầy đủ các phần mở rộ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/yi.json b/includes/installer/i18n/yi.json
index 47749b74..e1d2226c 100644
--- a/includes/installer/i18n/yi.json
+++ b/includes/installer/i18n/yi.json
@@ -2,14 +2,15 @@
"@metadata": {
"authors": [
"פוילישער",
- "පසිඳු කාවින්ද"
+ "පසිඳු කාවින්ද",
+ "Har-wradim"
]
},
"config-desc": "דער אינסטאלירער פאר מעדיעוויקי",
"config-title": "מעדיעוויקי $1 אינסטאלירונג",
"config-information": "אינפֿארמאציע",
"config-localsettings-badkey": "דעם שליסל וואס איר האט אײַנגעגעבן איז פאלש.",
- "config-session-error": "פֿעלער ביים אנהייבן סעסיע:$1",
+ "config-session-error": "פֿעלער ביים אָנהייבן סעסיע: $1",
"config-your-language": "אײַער שפראך:",
"config-your-language-help": "קלויבט א שפראך צו ניצן ביים אינסטאלירונג פראצעס.",
"config-wiki-language": "ווקי שפראך:",
diff --git a/includes/installer/i18n/zh-hans.json b/includes/installer/i18n/zh-hans.json
index f4dbdd78..cbd48f15 100644
--- a/includes/installer/i18n/zh-hans.json
+++ b/includes/installer/i18n/zh-hans.json
@@ -21,7 +21,8 @@
"Zjzengdongyang",
"Mywood",
"Impersonator 1",
- "Fengchao"
+ "Fengchao",
+ "Duolaimi"
]
},
"config-desc": "MediaWiki安装程序",
@@ -69,9 +70,9 @@
"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-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-no-db": "无法找到合适的数据库驱动!您需要为PHP安装数据库驱动。目前支持以下数据库{{PLURAL:$2|类型}}:$1。\n\n如果您自己编译了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此页]。",
@@ -147,7 +148,7 @@
"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-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/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中])",
@@ -244,7 +245,7 @@
"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-sa": "知识共享“署名-相同方式共享”",
"config-license-cc-by": "知识共享署名",
"config-license-cc-by-nc-sa": "知识共享署名-非商业性使用-相同方式共享",
"config-license-cc-0": "知识共享Zero(公有领域)",
diff --git a/includes/installer/i18n/zh-hant.json b/includes/installer/i18n/zh-hant.json
index b860dc6f..4c7b0c50 100644
--- a/includes/installer/i18n/zh-hant.json
+++ b/includes/installer/i18n/zh-hant.json
@@ -59,11 +59,10 @@
"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-no-db": "找不到合適的資料庫驅動程式!您需要安裝 PHP 資料庫驅動程式。\n目前支援以下{{PLURAL:$2|類型|類型}}的資料庫: $1 。\n\n如果您是自行編譯 PHP,您必須重新設定並開啟資料庫客戶端,例:使用 <code>./configure --with-mysqli</code> 指令參數。\n如果您是使用 Debian 或 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] 以取得操作說明。",
@@ -217,14 +216,14 @@
"config-admin-name-invalid": "指定的使用者名稱 \"<nowiki>$1</nowiki>\" 無效,請改用其他使用者名稱。",
"config-admin-password-blank": "輸入管理員帳號密碼。",
"config-admin-password-mismatch": "兩次輸入的密碼並不相同。",
- "config-admin-email": "電子郵件位址:",
+ "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-admin-error-bademail": "您輸入了不正確的電子郵件地址。",
"config-subscribe": "訂閱 [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 發佈公告郵寄清單]。",
"config-subscribe-help": "這是一個用於發佈公告的低郵件量郵寄清單,內容包括重要的安全公告。\n您應該訂閱它並在 MediaWiki 發佈新版的時候更新系統。",
- "config-subscribe-noemail": "您正嘗試不填寫電子郵件位址訂閱發佈公告郵寄清單。 \n請如果您希望訂閱郵寄清單,請提供一個有效的電子郵件位址。",
+ "config-subscribe-noemail": "您正嘗試不填寫電子郵件地址訂閱發佈公告郵寄清單。 \n請如果您希望訂閱郵寄清單,請提供一個有效的電子郵件地址。",
"config-almost-done": "您快要完成了!\n您現在可以跳過其餘的設定項目並且立即安裝 Wiki。",
"config-optional-continue": "多問我一些問題吧。",
"config-optional-skip": "我已經不耐煩了,請趕緊安裝 Wiki。",
@@ -243,7 +242,7 @@
"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-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如果您不需要使用電子郵件功能,請在此處關閉。",
@@ -285,10 +284,10 @@
"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-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": "完成",
@@ -318,6 +317,8 @@
"config-install-stats": "初始化統計資訊",
"config-install-keys": "產生秘密金鑰中",
"config-insecure-keys": "<strong>警告:</strong>在安裝過程中所產生的 $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> 設定項目。",
@@ -327,7 +328,7 @@
"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-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>",
diff --git a/includes/interwiki/Interwiki.php b/includes/interwiki/Interwiki.php
index 55b25069..02fbb080 100644
--- a/includes/interwiki/Interwiki.php
+++ b/includes/interwiki/Interwiki.php
@@ -19,6 +19,8 @@
*
* @file
*/
+use \Cdb\Exception as CdbException;
+use \Cdb\Reader as CdbReader;
/**
* The interwiki class
@@ -192,7 +194,7 @@ class Interwiki {
global $wgMemc, $wgInterwikiExpiry;
$iwData = array();
- if ( !wfRunHooks( 'InterwikiLoadPrefix', array( $prefix, &$iwData ) ) ) {
+ if ( !Hooks::run( 'InterwikiLoadPrefix', array( $prefix, &$iwData ) ) ) {
return Interwiki::loadFromArray( $iwData );
}
diff --git a/includes/jobqueue/Job.php b/includes/jobqueue/Job.php
index ee3f2c2b..f8de0b5d 100644
--- a/includes/jobqueue/Job.php
+++ b/includes/jobqueue/Job.php
@@ -47,20 +47,12 @@ abstract class Job implements IJobSpecification {
/** @var string Text for error that occurred last */
protected $error;
- /*-------------------------------------------------------------------------
- * Abstract functions
- *------------------------------------------------------------------------*/
-
/**
* Run the job
* @return bool Success
*/
abstract public function run();
- /*-------------------------------------------------------------------------
- * Static functions
- *------------------------------------------------------------------------*/
-
/**
* Create the appropriate object to handle a specific job
*
@@ -81,80 +73,37 @@ abstract class Job implements IJobSpecification {
}
/**
+ * @param string $command
+ * @param Title $title
+ * @param array|bool $params Can not be === true
+ */
+ public function __construct( $command, $title, $params = false ) {
+ $this->command = $command;
+ $this->title = $title;
+ $this->params = $params;
+
+ // expensive jobs may set this to true
+ $this->removeDuplicates = false;
+ }
+
+ /**
* Batch-insert a group of jobs into the queue.
* This will be wrapped in a transaction with a forced commit.
*
* This may add duplicate at insert time, but they will be
* removed later on, when the first one is popped.
*
- * @param array $jobs Array of Job objects
+ * @param Job[] $jobs Array of Job objects
* @return bool
* @deprecated since 1.21
*/
public static function batchInsert( $jobs ) {
+ wfDeprecated( __METHOD__, '1.21' );
JobQueueGroup::singleton()->push( $jobs );
return true;
}
/**
- * Insert a group of jobs into the queue.
- *
- * Same as batchInsert() but does not commit and can thus
- * be rolled-back as part of a larger transaction. However,
- * large batches of jobs can cause slave lag.
- *
- * @param array $jobs Array of Job objects
- * @return bool
- * @deprecated since 1.21
- */
- public static function safeBatchInsert( $jobs ) {
- JobQueueGroup::singleton()->push( $jobs, JobQueue::QOS_ATOMIC );
- return true;
- }
-
- /**
- * Pop a job of a certain type. This tries less hard than pop() to
- * actually find a job; it may be adversely affected by concurrent job
- * runners.
- *
- * @param string $type
- * @return Job|bool Returns false if there are no jobs
- * @deprecated since 1.21
- */
- public static function pop_type( $type ) {
- return JobQueueGroup::singleton()->get( $type )->pop();
- }
-
- /**
- * Pop a job off the front of the queue.
- * This is subject to $wgJobTypesExcludedFromDefaultQueue.
- *
- * @return Job|bool False if there are no jobs
- * @deprecated since 1.21
- */
- public static function pop() {
- return JobQueueGroup::singleton()->pop();
- }
-
- /*-------------------------------------------------------------------------
- * Non-static functions
- *------------------------------------------------------------------------*/
-
- /**
- * @param string $command
- * @param Title $title
- * @param array|bool $params
- */
- public function __construct( $command, $title, $params = false ) {
- $this->command = $command;
- $this->title = $title;
- $this->params = $params;
-
- // expensive jobs may set this to true
- $this->removeDuplicates = false;
- }
-
- /**
* @return string
*/
public function getType() {
@@ -186,7 +135,15 @@ abstract class Job implements IJobSpecification {
}
/**
- * @return bool Whether only one of each identical set of jobs should be run
+ * Whether the queue should reject insertion of this job if a duplicate exists
+ *
+ * This can be used to avoid duplicated effort or combined with delayed jobs to
+ * coalesce updates into larger batches. Claimed jobs are never treated as
+ * duplicates of new jobs, and some queues may allow a few duplicates due to
+ * network partitions and fail-over. Thus, additional locking is needed to
+ * enforce mutual exclusion if this is really needed.
+ *
+ * @return bool
*/
public function ignoreDuplicates() {
return $this->removeDuplicates;
@@ -231,6 +188,8 @@ abstract class Job implements IJobSpecification {
unset( $info['params']['rootJobTimestamp'] );
// Likewise for jobs with different delay times
unset( $info['params']['jobReleaseTimestamp'] );
+ // Queues pack and hash this array, so normalize the order
+ ksort( $info['params'] );
}
return $info;
@@ -315,7 +274,7 @@ abstract class Job implements IJobSpecification {
break;
}
}
- if ( $filteredValue ) {
+ if ( $filteredValue && count( $filteredValue ) < 10 ) {
$value = FormatJson::encode( $filteredValue );
} else {
$value = "array(" . count( $value ) . ")";
@@ -328,16 +287,25 @@ abstract class Job implements IJobSpecification {
}
}
- if ( is_object( $this->title ) ) {
- $s = "{$this->command} " . $this->title->getPrefixedDBkey();
- if ( $paramString !== '' ) {
- $s .= ' ' . $paramString;
+ $metaString = '';
+ foreach ( $this->metadata as $key => $value ) {
+ if ( is_scalar( $value ) && mb_strlen( $value ) < 1024 ) {
+ $metaString .= ( $metaString ? ",$key=$value" : "$key=$value" );
}
+ }
- return $s;
- } else {
- return "{$this->command} $paramString";
+ $s = $this->command;
+ if ( is_object( $this->title ) ) {
+ $s .= " {$this->title->getPrefixedDBkey()}";
}
+ if ( $paramString != '' ) {
+ $s .= " $paramString";
+ }
+ if ( $metaString != '' ) {
+ $s .= " ($metaString)";
+ }
+
+ return $s;
}
protected function setLastError( $error ) {
diff --git a/includes/jobqueue/JobQueue.php b/includes/jobqueue/JobQueue.php
index c00d22e9..91fe86cf 100644
--- a/includes/jobqueue/JobQueue.php
+++ b/includes/jobqueue/JobQueue.php
@@ -44,11 +44,10 @@ abstract class JobQueue {
/** @var int Maximum number of times to try a job */
protected $maxTries;
- /** @var bool Allow delayed jobs */
- protected $checkDelay;
-
/** @var BagOStuff */
protected $dupCache;
+ /** @var JobQueueAggregator */
+ protected $aggr;
const QOS_ATOMIC = 1; // integer; "all-or-nothing" job insertions
@@ -71,11 +70,10 @@ abstract class JobQueue {
if ( !in_array( $this->order, $this->supportedOrders() ) ) {
throw new MWException( __CLASS__ . " does not support '{$this->order}' order." );
}
- $this->checkDelay = !empty( $params['checkDelay'] );
- if ( $this->checkDelay && !$this->supportsDelayedJobs() ) {
- throw new MWException( __CLASS__ . " does not support delayed jobs." );
- }
$this->dupCache = wfGetCache( CACHE_ANYTHING );
+ $this->aggr = isset( $params['aggregator'] )
+ ? $params['aggregator']
+ : new JobQueueAggregatorNull( array() );
}
/**
@@ -98,10 +96,6 @@ abstract class JobQueue {
* but not acknowledged as completed after this many seconds. Recycling
* of jobs simple means re-inserting them into the queue. Jobs can be
* attempted up to three times before being discarded.
- * - checkDelay : If supported, respect Job::getReleaseTimestamp() in the push functions.
- * This lets delayed jobs wait in a staging area until a given timestamp is
- * reached, at which point they will enter the queue. If this is not enabled
- * or not supported, an exception will be thrown on delayed job insertion.
*
* Queue classes should throw an exception if they do not support the options given.
*
@@ -144,14 +138,6 @@ abstract class JobQueue {
}
/**
- * @return bool Whether delayed jobs are enabled
- * @since 1.22
- */
- final public function delayedJobsEnabled() {
- return $this->checkDelay;
- }
-
- /**
* Get the allowed queue orders for configuration validation
*
* @return array Subset of (random, timestamp, fifo, undefined)
@@ -175,6 +161,14 @@ abstract class JobQueue {
}
/**
+ * @return bool Whether delayed jobs are enabled
+ * @since 1.22
+ */
+ final public function delayedJobsEnabled() {
+ return $this->supportsDelayedJobs();
+ }
+
+ /**
* Quickly check if the queue has no available (unacquired, non-delayed) jobs.
* Queue classes should use caching if they are any slower without memcached.
*
@@ -187,9 +181,7 @@ abstract class JobQueue {
* @throws JobQueueError
*/
final public function isEmpty() {
- wfProfileIn( __METHOD__ );
$res = $this->doIsEmpty();
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -210,9 +202,7 @@ abstract class JobQueue {
* @throws JobQueueError
*/
final public function getSize() {
- wfProfileIn( __METHOD__ );
$res = $this->doGetSize();
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -233,9 +223,7 @@ abstract class JobQueue {
* @throws JobQueueError
*/
final public function getAcquiredCount() {
- wfProfileIn( __METHOD__ );
$res = $this->doGetAcquiredCount();
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -257,9 +245,7 @@ abstract class JobQueue {
* @since 1.22
*/
final public function getDelayedCount() {
- wfProfileIn( __METHOD__ );
$res = $this->doGetDelayedCount();
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -282,9 +268,7 @@ abstract class JobQueue {
* @throws JobQueueError
*/
final public function getAbandonedCount() {
- wfProfileIn( __METHOD__ );
$res = $this->doGetAbandonedCount();
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -308,7 +292,8 @@ abstract class JobQueue {
* @throws JobQueueError
*/
final public function push( $jobs, $flags = 0 ) {
- $this->batchPush( is_array( $jobs ) ? $jobs : array( $jobs ), $flags );
+ $jobs = is_array( $jobs ) ? $jobs : array( $jobs );
+ $this->batchPush( $jobs, $flags );
}
/**
@@ -330,15 +315,14 @@ abstract class JobQueue {
if ( $job->getType() !== $this->type ) {
throw new MWException(
"Got '{$job->getType()}' job; expected a '{$this->type}' job." );
- } elseif ( $job->getReleaseTimestamp() && !$this->checkDelay ) {
+ } elseif ( $job->getReleaseTimestamp() && !$this->supportsDelayedJobs() ) {
throw new MWException(
"Got delayed '{$job->getType()}' job; delays are not supported." );
}
}
- wfProfileIn( __METHOD__ );
$this->doBatchPush( $jobs, $flags );
- wfProfileOut( __METHOD__ );
+ $this->aggr->notifyQueueNonEmpty( $this->wiki, $this->type );
}
/**
@@ -366,9 +350,11 @@ abstract class JobQueue {
throw new MWException( "Unrecognized job type '{$this->type}'." );
}
- wfProfileIn( __METHOD__ );
$job = $this->doPop();
- wfProfileOut( __METHOD__ );
+
+ if ( !$job ) {
+ $this->aggr->notifyQueueEmpty( $this->wiki, $this->type );
+ }
// Flag this job as an old duplicate based on its "root" job...
try {
@@ -376,7 +362,7 @@ abstract class JobQueue {
JobQueue::incrStats( 'job-pop-duplicate', $this->type, 1, $this->wiki );
$job = DuplicateJob::newFromJob( $job ); // convert to a no-op
}
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// don't lose jobs over this
}
@@ -403,9 +389,7 @@ abstract class JobQueue {
if ( $job->getType() !== $this->type ) {
throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
- wfProfileIn( __METHOD__ );
$this->doAck( $job );
- wfProfileOut( __METHOD__ );
}
/**
@@ -449,9 +433,7 @@ abstract class JobQueue {
if ( $job->getType() !== $this->type ) {
throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
- wfProfileIn( __METHOD__ );
$ok = $this->doDeduplicateRootJob( $job );
- wfProfileOut( __METHOD__ );
return $ok;
}
@@ -494,9 +476,7 @@ abstract class JobQueue {
if ( $job->getType() !== $this->type ) {
throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
- wfProfileIn( __METHOD__ );
$isDuplicate = $this->doIsRootJobOldDuplicate( $job );
- wfProfileOut( __METHOD__ );
return $isDuplicate;
}
@@ -538,9 +518,7 @@ abstract class JobQueue {
* @return void
*/
final public function delete() {
- wfProfileIn( __METHOD__ );
$this->doDelete();
- wfProfileOut( __METHOD__ );
}
/**
@@ -560,9 +538,7 @@ abstract class JobQueue {
* @throws JobQueueError
*/
final public function waitForBackups() {
- wfProfileIn( __METHOD__ );
$this->doWaitForBackups();
- wfProfileOut( __METHOD__ );
}
/**
@@ -607,9 +583,7 @@ abstract class JobQueue {
* @return void
*/
final public function flushCaches() {
- wfProfileIn( __METHOD__ );
$this->doFlushCaches();
- wfProfileOut( __METHOD__ );
}
/**
@@ -642,6 +616,17 @@ abstract class JobQueue {
}
/**
+ * Get an iterator to traverse over all abandoned jobs in this queue
+ *
+ * @return Iterator
+ * @throws JobQueueError
+ * @since 1.25
+ */
+ public function getAllAbandonedJobs() {
+ return new ArrayIterator( array() ); // not implemented
+ }
+
+ /**
* Do not use this function outside of JobQueue/JobQueueGroup
*
* @return string
@@ -661,7 +646,6 @@ abstract class JobQueue {
* @since 1.22
*/
final public function getSiblingQueuesWithJobs( array $types ) {
- $section = new ProfileSection( __METHOD__ );
return $this->doGetSiblingQueuesWithJobs( $types );
}
@@ -686,7 +670,6 @@ abstract class JobQueue {
* @since 1.22
*/
final public function getSiblingQueueSizes( array $types ) {
- $section = new ProfileSection( __METHOD__ );
return $this->doGetSiblingQueueSizes( $types );
}
diff --git a/includes/jobqueue/JobQueueDB.php b/includes/jobqueue/JobQueueDB.php
index 08873cc1..d5f47ffd 100644
--- a/includes/jobqueue/JobQueueDB.php
+++ b/includes/jobqueue/JobQueueDB.php
@@ -221,7 +221,7 @@ class JobQueueDB extends JobQueue {
}
$rowSet = array(); // (sha1 => job) map for jobs that are de-duplicated
- $rowList = array(); // list of jobs for jobs that are are not de-duplicated
+ $rowList = array(); // list of jobs for jobs that are not de-duplicated
foreach ( $jobs as $job ) {
$row = $this->insertFields( $job );
if ( $job->ignoreDuplicates() ) {
@@ -556,7 +556,7 @@ class JobQueueDB extends JobQueue {
* @return void
*/
protected function doWaitForBackups() {
- wfWaitForSlaves();
+ wfWaitForSlaves( false, $this->wiki, $this->cluster ?: false );
}
/**
@@ -686,7 +686,9 @@ class JobQueueDB extends JobQueue {
$affected = $dbw->affectedRows();
$count += $affected;
JobQueue::incrStats( 'job-recycle', $this->type, $affected, $this->wiki );
+ // The tasks recycled jobs or release delayed jobs into the queue
$this->cache->set( $this->getCacheKey( 'empty' ), 'false', self::CACHE_TTL_LONG );
+ $this->aggr->notifyQueueNonEmpty( $this->wiki, $this->type );
}
}
diff --git a/includes/jobqueue/JobQueueFederated.php b/includes/jobqueue/JobQueueFederated.php
index c4301eed..d985d449 100644
--- a/includes/jobqueue/JobQueueFederated.php
+++ b/includes/jobqueue/JobQueueFederated.php
@@ -49,20 +49,12 @@
class JobQueueFederated extends JobQueue {
/** @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
-
/**
* @param array $params Possible keys:
* - sectionsByWiki : A map of wiki IDs to section names.
@@ -72,10 +64,8 @@ class JobQueueFederated extends JobQueue {
* have explicitly defined sections.
* - configByPartition : Map of queue partition names to configuration arrays.
* These configuration arrays are passed to JobQueue::factory().
- * The options set here are overriden by those passed to this
+ * The options set here are overridden by those passed to this
* 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.
* - 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
@@ -96,17 +86,10 @@ class JobQueueFederated extends JobQueue {
// Get the full partition map
$partitionMap = $params['partitionsBySection'][$section];
arsort( $partitionMap, SORT_NUMERIC );
- // Get the partitions jobs can actually be pushed to
- $partitionPushMap = $partitionMap;
- if ( isset( $params['partitionsNoPush'] ) ) {
- foreach ( $params['partitionsNoPush'] as $partition ) {
- unset( $partitionPushMap[$partition] );
- }
- }
// Get the config to pass to merge into each partition queue config
$baseConfig = $params;
foreach ( array( 'class', 'sectionsByWiki', 'maxPartitionsTry',
- 'partitionsBySection', 'configByPartition', 'partitionsNoPush' ) as $o
+ 'partitionsBySection', 'configByPartition', ) as $o
) {
unset( $baseConfig[$o] ); // partition queue doesn't care about this
}
@@ -120,14 +103,6 @@ class JobQueueFederated extends JobQueue {
}
// Ring of all partitions
$this->partitionRing = new HashRing( $partitionMap );
- // Get the ring of partitions to push jobs into
- 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( $partitionMap ) > 1 ? wfGetMainCache() : new EmptyBagOStuff();
}
protected function supportedOrders() {
@@ -140,19 +115,16 @@ class JobQueueFederated extends JobQueue {
}
protected function supportsDelayedJobs() {
- return true; // defer checks to the partitions
+ foreach ( $this->partitionQueues as $queue ) {
+ if ( !$queue->supportsDelayedJobs() ) {
+ return false;
+ }
+ }
+
+ return true;
}
protected function doIsEmpty() {
- $key = $this->getCacheKey( 'empty' );
-
- $isEmpty = $this->cache->get( $key );
- if ( $isEmpty === 'true' ) {
- return true;
- } elseif ( $isEmpty === 'false' ) {
- return false;
- }
-
$empty = true;
$failed = 0;
foreach ( $this->partitionQueues as $queue ) {
@@ -160,12 +132,11 @@ class JobQueueFederated extends JobQueue {
$empty = $empty && $queue->doIsEmpty();
} catch ( JobQueueError $e ) {
++$failed;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
}
}
$this->throwErrorIfAllPartitionsDown( $failed );
- $this->cache->add( $key, $empty ? 'true' : 'false', self::CACHE_TTL_LONG );
return $empty;
}
@@ -191,32 +162,24 @@ class JobQueueFederated extends JobQueue {
* @return int
*/
protected function getCrossPartitionSum( $type, $method ) {
- $key = $this->getCacheKey( $type );
-
- $count = $this->cache->get( $key );
- 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->logException( $e );
}
}
$this->throwErrorIfAllPartitionsDown( $failed );
- $this->cache->set( $key, $count, self::CACHE_TTL_SHORT );
-
return $count;
}
protected function doBatchPush( array $jobs, $flags ) {
// Local ring variable that may be changed to point to a new ring on failure
- $partitionRing = $this->partitionPushRing;
+ $partitionRing = $this->partitionRing;
// Try to insert the jobs and update $partitionsTry on any failures.
// Retry to insert any remaning jobs again, ignoring the bad partitions.
$jobsLeft = $jobs;
@@ -277,12 +240,9 @@ class JobQueueFederated extends JobQueue {
$queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
} catch ( JobQueueError $e ) {
$ok = false;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
}
- if ( $ok ) {
- $key = $this->getCacheKey( 'empty' );
- $this->cache->set( $key, 'false', self::CACHE_TTL_LONG );
- } else {
+ if ( !$ok ) {
if ( !$partitionRing->ejectFromLiveRing( $partition, 5 ) ) { // blacklist
throw new JobQueueError( "Could not insert job(s), no partitions available." );
}
@@ -299,12 +259,9 @@ class JobQueueFederated extends JobQueue {
$queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
} catch ( JobQueueError $e ) {
$ok = false;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
}
- if ( $ok ) {
- $key = $this->getCacheKey( 'empty' );
- $this->cache->set( $key, 'false', self::CACHE_TTL_LONG );
- } else {
+ if ( !$ok ) {
if ( !$partitionRing->ejectFromLiveRing( $partition, 5 ) ) { // blacklist
throw new JobQueueError( "Could not insert job(s), no partitions available." );
}
@@ -331,7 +288,7 @@ class JobQueueFederated extends JobQueue {
$job = $queue->pop();
} catch ( JobQueueError $e ) {
++$failed;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
$job = false;
}
if ( $job ) {
@@ -344,9 +301,6 @@ class JobQueueFederated extends JobQueue {
}
$this->throwErrorIfAllPartitionsDown( $failed );
- $key = $this->getCacheKey( 'empty' );
- $this->cache->set( $key, 'true', self::CACHE_TTL_LONG );
-
return false;
}
@@ -361,12 +315,12 @@ class JobQueueFederated extends JobQueue {
protected function doIsRootJobOldDuplicate( Job $job ) {
$params = $job->getRootJobParams();
$sigature = $params['rootJobSignature'];
- $partition = $this->partitionPushRing->getLiveLocation( $sigature );
+ $partition = $this->partitionRing->getLiveLocation( $sigature );
try {
return $this->partitionQueues[$partition]->doIsRootJobOldDuplicate( $job );
} catch ( JobQueueError $e ) {
- if ( $this->partitionPushRing->ejectFromLiveRing( $partition, 5 ) ) {
- $partition = $this->partitionPushRing->getLiveLocation( $sigature );
+ if ( $this->partitionRing->ejectFromLiveRing( $partition, 5 ) ) {
+ $partition = $this->partitionRing->getLiveLocation( $sigature );
return $this->partitionQueues[$partition]->doIsRootJobOldDuplicate( $job );
}
}
@@ -377,12 +331,12 @@ class JobQueueFederated extends JobQueue {
protected function doDeduplicateRootJob( Job $job ) {
$params = $job->getRootJobParams();
$sigature = $params['rootJobSignature'];
- $partition = $this->partitionPushRing->getLiveLocation( $sigature );
+ $partition = $this->partitionRing->getLiveLocation( $sigature );
try {
return $this->partitionQueues[$partition]->doDeduplicateRootJob( $job );
} catch ( JobQueueError $e ) {
- if ( $this->partitionPushRing->ejectFromLiveRing( $partition, 5 ) ) {
- $partition = $this->partitionPushRing->getLiveLocation( $sigature );
+ if ( $this->partitionRing->ejectFromLiveRing( $partition, 5 ) ) {
+ $partition = $this->partitionRing->getLiveLocation( $sigature );
return $this->partitionQueues[$partition]->doDeduplicateRootJob( $job );
}
}
@@ -398,7 +352,7 @@ class JobQueueFederated extends JobQueue {
$queue->doDelete();
} catch ( JobQueueError $e ) {
++$failed;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
}
}
$this->throwErrorIfAllPartitionsDown( $failed );
@@ -413,7 +367,7 @@ class JobQueueFederated extends JobQueue {
$queue->waitForBackups();
} catch ( JobQueueError $e ) {
++$failed;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
}
}
$this->throwErrorIfAllPartitionsDown( $failed );
@@ -440,10 +394,6 @@ class JobQueueFederated extends JobQueue {
'abandonedcount'
);
- foreach ( $types as $type ) {
- $this->cache->delete( $this->getCacheKey( $type ) );
- }
-
/** @var JobQueue $queue */
foreach ( $this->partitionQueues as $queue ) {
$queue->doFlushCaches();
@@ -472,6 +422,17 @@ class JobQueueFederated extends JobQueue {
return $iterator;
}
+ public function getAllAbandonedJobs() {
+ $iterator = new AppendIterator();
+
+ /** @var JobQueue $queue */
+ foreach ( $this->partitionQueues as $queue ) {
+ $iterator->append( $queue->getAllAbandonedJobs() );
+ }
+
+ return $iterator;
+ }
+
public function getCoalesceLocationInternal() {
return "JobQueueFederated:wiki:{$this->wiki}" .
sha1( serialize( array_keys( $this->partitionQueues ) ) );
@@ -495,7 +456,7 @@ class JobQueueFederated extends JobQueue {
}
} catch ( JobQueueError $e ) {
++$failed;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
}
}
$this->throwErrorIfAllPartitionsDown( $failed );
@@ -519,7 +480,7 @@ class JobQueueFederated extends JobQueue {
}
} catch ( JobQueueError $e ) {
++$failed;
- MWExceptionHandler::logException( $e );
+ $this->logException( $e );
}
}
$this->throwErrorIfAllPartitionsDown( $failed );
@@ -527,6 +488,10 @@ class JobQueueFederated extends JobQueue {
return $result;
}
+ protected function logException( Exception $e ) {
+ wfDebugLog( 'JobQueueFederated', $e->getMessage() . "\n" . $e->getTraceAsString() );
+ }
+
/**
* Throw an error if no partitions available
*
@@ -546,14 +511,4 @@ class JobQueueFederated extends JobQueue {
$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/jobqueue/JobQueueGroup.php b/includes/jobqueue/JobQueueGroup.php
index 98a78c5e..ebd547a0 100644
--- a/includes/jobqueue/JobQueueGroup.php
+++ b/includes/jobqueue/JobQueueGroup.php
@@ -94,6 +94,7 @@ class JobQueueGroup {
} else {
$conf = $conf + $wgJobTypeConf['default'];
}
+ $conf['aggregator'] = JobQueueAggregator::singleton();
return JobQueue::factory( $conf );
}
@@ -104,7 +105,7 @@ class JobQueueGroup {
* This inserts the jobs into the queue specified by $wgJobTypeConf
* and updates the aggregate job queue information cache as needed.
*
- * @param Job|array $jobs A single Job or a list of Jobs
+ * @param Job|Job[] $jobs A single Job or a list of Jobs
* @throws MWException
* @return void
*/
@@ -125,7 +126,6 @@ class JobQueueGroup {
foreach ( $jobsByType as $type => $jobs ) {
$this->get( $type )->push( $jobs );
- JobQueueAggregator::singleton()->notifyQueueNonEmpty( $this->wiki, $type );
}
if ( $this->cache->has( 'queues-ready', 'list' ) ) {
@@ -153,9 +153,6 @@ class JobQueueGroup {
if ( is_string( $qtype ) ) { // specific job type
if ( !in_array( $qtype, $blacklist ) ) {
$job = $this->get( $qtype )->pop();
- if ( !$job ) {
- JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $qtype );
- }
}
} else { // any job in the "default" jobs types
if ( $flags & self::USE_CACHE ) {
@@ -179,7 +176,6 @@ class JobQueueGroup {
if ( $job ) { // found
break;
} else { // not found
- JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $type );
$this->cache->clear( 'queues-ready' );
}
}
@@ -220,12 +216,10 @@ class JobQueueGroup {
public function waitForBackups() {
global $wgJobTypeConf;
- wfProfileIn( __METHOD__ );
// Try to avoid doing this more than once per queue storage medium
foreach ( $wgJobTypeConf as $type => $conf ) {
$this->get( $type )->waitForBackups();
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -383,10 +377,6 @@ 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 ) {
diff --git a/includes/jobqueue/JobQueueRedis.php b/includes/jobqueue/JobQueueRedis.php
index 3519eac8..6c823fb9 100644
--- a/includes/jobqueue/JobQueueRedis.php
+++ b/includes/jobqueue/JobQueueRedis.php
@@ -24,7 +24,7 @@
/**
* Class to handle job queues stored in Redis
*
- * This is faster, less resource intensive, queue that JobQueueDB.
+ * This is a faster and less resource-intensive job queue than JobQueueDB.
* All data for a queue using this class is placed into one redis server.
*
* There are eight main redis keys used to track jobs:
@@ -49,7 +49,7 @@
*
* This class requires Redis 2.6 as it makes use Lua scripts for fast atomic operations.
* Additionally, it should be noted that redis has different persistence modes, such
- * as rdb snapshots, journaling, and no persistent. Appropriate configuration should be
+ * as rdb snapshots, journaling, and no persistence. Appropriate configuration should be
* made on the servers based on what queues are using it and what tolerance they have.
*
* @ingroup JobQueue
@@ -64,8 +64,6 @@ class JobQueueRedis extends JobQueue {
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)
@@ -90,7 +88,11 @@ 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'] );
+ if ( empty( $params['daemonized'] ) ) {
+ throw new Exception(
+ "Non-daemonized mode is no longer supported. Please install the " .
+ "mediawiki/services/jobrunner service and update \$wgJobTypeConf as needed." );
+ }
}
protected function supportedOrders() {
@@ -134,9 +136,6 @@ class JobQueueRedis extends JobQueue {
* @throws JobQueueError
*/
protected function doGetAcquiredCount() {
- if ( $this->claimTTL <= 0 ) {
- return 0; // no acknowledgements
- }
$conn = $this->getConnection();
try {
$conn->multi( Redis::PIPELINE );
@@ -155,9 +154,6 @@ class JobQueueRedis extends JobQueue {
* @throws JobQueueError
*/
protected function doGetDelayedCount() {
- if ( !$this->checkDelay ) {
- return 0; // no delayed jobs
- }
$conn = $this->getConnection();
try {
return $conn->zSize( $this->getQueueKey( 'z-delayed' ) );
@@ -172,9 +168,6 @@ class JobQueueRedis extends JobQueue {
* @throws JobQueueError
*/
protected function doGetAbandonedCount() {
- if ( $this->claimTTL <= 0 ) {
- return 0; // no acknowledgements
- }
$conn = $this->getConnection();
try {
return $conn->zSize( $this->getQueueKey( 'z-abandoned' ) );
@@ -299,24 +292,10 @@ LUA;
protected function doPop() {
$job = false;
- // 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->recyclePruneAndUndelayJobs();
- }
-
$conn = $this->getConnection();
try {
do {
- if ( $this->claimTTL > 0 ) {
- // Keep the claimed job list down for high-traffic queues
- if ( mt_rand( 0, 99 ) == 0 ) {
- $this->recyclePruneAndUndelayJobs();
- }
- $blob = $this->popAndAcquireBlob( $conn );
- } else {
- $blob = $this->popAndDeleteBlob( $conn );
- }
+ $blob = $this->popAndAcquireBlob( $conn );
if ( !is_string( $blob ) ) {
break; // no jobs; nothing to do
}
@@ -328,7 +307,7 @@ LUA;
continue;
}
- // If $item is invalid, recyclePruneAndUndelayJobs() will cleanup as needed
+ // If $item is invalid, the runner loop recyling will cleanup as needed
$job = $this->getJobFromFields( $item ); // may be false
} while ( !$job ); // job may be false if invalid
} catch ( RedisException $e ) {
@@ -343,39 +322,6 @@ LUA;
* @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',kUnclaimed)
- if not id then return false end
- -- Get the job data and remove it
- local item = redis.call('hGet',kData,id)
- redis.call('hDel',kData,id)
- -- Allow new duplicates of this job
- 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;
- return $conn->luaEval( $script,
- array(
- $this->getQueueKey( 'l-unclaimed' ), # KEYS[1]
- $this->getQueueKey( 'h-sha1ById' ), # KEYS[2]
- $this->getQueueKey( 'h-idBySha1' ), # KEYS[3]
- $this->getQueueKey( 'h-data' ), # KEYS[4]
- ),
- 4 # number of first argument(s) that are keys
- );
- }
-
- /**
- * @param RedisConnRef $conn
- * @return array Serialized string or false
- * @throws RedisException
- */
protected function popAndAcquireBlob( RedisConnRef $conn ) {
static $script =
<<<LUA
@@ -416,36 +362,35 @@ LUA;
if ( !isset( $job->metadata['uuid'] ) ) {
throw new MWException( "Job of type '{$job->getType()}' has no UUID." );
}
- if ( $this->claimTTL > 0 ) {
- $conn = $this->getConnection();
- try {
- static $script =
+
+ $conn = $this->getConnection();
+ try {
+ static $script =
<<<LUA
- local kClaimed, kAttempts, kData = unpack(KEYS)
- -- Unmark the job as claimed
- redis.call('zRem',kClaimed,ARGV[1])
- redis.call('hDel',kAttempts,ARGV[1])
- -- Delete the job data itself
- return redis.call('hDel',kData,ARGV[1])
+ local kClaimed, kAttempts, kData = unpack(KEYS)
+ -- Unmark the job as claimed
+ redis.call('zRem',kClaimed,ARGV[1])
+ redis.call('hDel',kAttempts,ARGV[1])
+ -- Delete the job data itself
+ return redis.call('hDel',kData,ARGV[1])
LUA;
- $res = $conn->luaEval( $script,
- array(
- $this->getQueueKey( 'z-claimed' ), # KEYS[1]
- $this->getQueueKey( 'h-attempts' ), # KEYS[2]
- $this->getQueueKey( 'h-data' ), # KEYS[3]
- $job->metadata['uuid'] # ARGV[1]
- ),
- 3 # number of first argument(s) that are keys
- );
-
- if ( !$res ) {
- wfDebugLog( 'JobQueueRedis', "Could not acknowledge {$this->type} job." );
-
- return false;
- }
- } catch ( RedisException $e ) {
- $this->throwRedisException( $conn, $e );
+ $res = $conn->luaEval( $script,
+ array(
+ $this->getQueueKey( 'z-claimed' ), # KEYS[1]
+ $this->getQueueKey( 'h-attempts' ), # KEYS[2]
+ $this->getQueueKey( 'h-data' ), # KEYS[3]
+ $job->metadata['uuid'] # ARGV[1]
+ ),
+ 3 # number of first argument(s) that are keys
+ );
+
+ if ( !$res ) {
+ wfDebugLog( 'JobQueueRedis', "Could not acknowledge {$this->type} job." );
+
+ return false;
}
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $conn, $e );
}
return true;
@@ -571,6 +516,29 @@ LUA;
}
}
+ /**
+ * @see JobQueue::getAllAbandonedJobs()
+ * @return Iterator
+ */
+ public function getAllAbandonedJobs() {
+ $conn = $this->getConnection();
+ try {
+ $that = $this;
+
+ return new MappedIterator( // delayed jobs
+ $conn->zRange( $this->getQueueKey( 'z-abandoned' ), 0, -1 ),
+ function ( $uid ) use ( $that, $conn ) {
+ return $that->getJobFromUidInternal( $uid, $conn );
+ },
+ array( 'accept' => function ( $job ) {
+ return is_object( $job );
+ } )
+ );
+ } catch ( RedisException $e ) {
+ $this->throwRedisException( $conn, $e );
+ }
+ }
+
public function getCoalesceLocationInternal() {
return "RedisServer:" . $this->server;
}
@@ -630,115 +598,10 @@ LUA;
}
/**
- * Recycle or destroy any jobs that have been claimed for too long
- * and release any ready delayed jobs into the queue
- *
- * @return int Number of jobs recycled/deleted/undelayed
- * @throws MWException|JobQueueError
- */
- 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.
- // For those that cannot, they are marked as dead and kept around for
- // investigation and manual job restoration but are eventually deleted.
- $conn = $this->getConnection();
- try {
- $now = time();
- static $script =
-<<<LUA
- 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',kClaimed,0,ARGV[1])
- for k,id in ipairs(staleClaims) do
- 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',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',kAbandoned,timestamp,id)
- abandoned = abandoned + 1
- end
- 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',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',kAbandoned,id)
- redis.call('hDel',kAttempts,id)
- redis.call('hDel',kData,id)
- pruned = pruned + 1
- end
- -- 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(
- $this->getQueueKey( 'z-claimed' ), # KEYS[1]
- $this->getQueueKey( 'h-attempts' ), # KEYS[2]
- $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]
- $now # ARGV[4]
- ),
- 6 # number of first argument(s) that are keys
- );
- if ( $res ) {
- 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( $conn, $e );
- }
-
- return $count;
- }
-
- /**
* @return array
*/
protected function doGetPeriodicTasks() {
- if ( $this->daemonized ) {
- return array(); // managed in the runner loop
- }
- $periods = array( 3600 ); // standard cleanup (useful on config change)
- if ( $this->claimTTL > 0 ) {
- $periods[] = ceil( $this->claimTTL / 2 ); // avoid bad timing
- }
- if ( $this->checkDelay ) {
- $periods[] = 300; // 5 minutes
- }
- $period = min( $periods );
- $period = max( $period, 30 ); // sanity
-
- return array(
- 'recyclePruneAndUndelayJobs' => array(
- 'callback' => array( $this, 'recyclePruneAndUndelayJobs' ),
- 'period' => $period,
- )
- );
+ return array(); // managed in the runner loop
}
/**
diff --git a/includes/jobqueue/JobRunner.php b/includes/jobqueue/JobRunner.php
index 8cccedaf..b8c5d6cf 100644
--- a/includes/jobqueue/JobRunner.php
+++ b/includes/jobqueue/JobRunner.php
@@ -21,13 +21,17 @@
* @ingroup JobQueue
*/
+use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+
/**
* Job queue runner utility methods
*
* @ingroup JobQueue
* @since 1.24
*/
-class JobRunner {
+class JobRunner implements LoggerAwareInterface {
/** @var callable|null Debug output handler */
protected $debug;
@@ -39,6 +43,28 @@ class JobRunner {
}
/**
+ * @var LoggerInterface $logger
+ */
+ protected $logger;
+
+ /**
+ * @param LoggerInterface $logger
+ */
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * @param LoggerInterface $logger
+ */
+ public function __construct( LoggerInterface $logger = null ) {
+ if ( $logger === null ) {
+ $logger = LoggerFactory::getInstance( 'runJobs' );
+ }
+ $this->setLogger( $logger );
+ }
+
+ /**
* 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:
@@ -62,6 +88,8 @@ class JobRunner {
* @return array Summary response that can easily be JSON serialized
*/
public function run( array $options ) {
+ global $wgJobClasses;
+
$response = array( 'jobs' => array(), 'reached' => 'none-ready' );
$type = isset( $options['type'] ) ? $options['type'] : false;
@@ -69,11 +97,31 @@ class JobRunner {
$maxTime = isset( $options['maxTime'] ) ? $options['maxTime'] : false;
$noThrottle = isset( $options['throttle'] ) && !$options['throttle'];
+ if ( $type !== false && !isset( $wgJobClasses[$type] ) ) {
+ $response['reached'] = 'none-possible';
+ return $response;
+ }
+
$group = JobQueueGroup::singleton();
// Handle any required periodic queue maintenance
$count = $group->executeReadyPeriodicTasks();
if ( $count > 0 ) {
- $this->runJobsLog( "Executed $count periodic queue task(s)." );
+ $msg = "Executed $count periodic queue task(s).";
+ $this->logger->debug( $msg );
+ $this->debugCallback( $msg );
+ }
+
+ // Bail out if in read-only mode
+ if ( wfReadOnly() ) {
+ $response['reached'] = 'read-only';
+ return $response;
+ }
+
+ // Bail out if there is too much DB lag
+ list( , $maxLag ) = wfGetLBFactory()->getMainLB( wfWikiID() )->getMaxLag();
+ if ( $maxLag >= 5 ) {
+ $response['reached'] = 'slave-lag-limit';
+ return $response;
}
// Flush any pending DB writes for sanity
@@ -87,8 +135,10 @@ class JobRunner {
$jobsRun = 0;
$timeMsTotal = 0;
$flags = JobQueueGroup::USE_CACHE;
+ $checkPeriod = 5.0; // seconds
+ $checkPhase = mt_rand( 0, 1000 * $checkPeriod ) / 1000; // avoid stampedes
$startTime = microtime( true ); // time since jobs started running
- $lastTime = microtime( true ); // time since last slave check
+ $lastTime = microtime( true ) - $checkPhase; // time since last slave check
do {
// Sync the persistent backoffs with concurrent runners
$backoffs = $this->syncBackoffDeltas( $backoffs, $backoffDeltas, $wait );
@@ -117,24 +167,24 @@ class JobRunner {
$backoffs = $this->syncBackoffDeltas( $backoffs, $backoffDeltas, $wait );
}
- $this->runJobsLog( $job->toString() . " STARTING" );
+ $msg = $job->toString() . " STARTING";
+ $this->logger->info( $msg );
+ $this->debugCallback( $msg );
// Run the job...
- wfProfileIn( __METHOD__ . '-' . get_class( $job ) );
$jobStartTime = microtime( true );
try {
++$jobsRun;
$status = $job->run();
$error = $job->getLastError();
wfGetLBFactory()->commitMasterChanges();
- } catch ( MWException $e ) {
+ } catch ( Exception $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
@@ -151,9 +201,13 @@ class JobRunner {
}
if ( $status === false ) {
- $this->runJobsLog( $job->toString() . " t=$timeMs error={$error}" );
+ $msg = $job->toString() . " t=$timeMs error={$error}";
+ $this->logger->error( $msg );
+ $this->debugCallback( $msg );
} else {
- $this->runJobsLog( $job->toString() . " t=$timeMs good" );
+ $msg = $job->toString() . " t=$timeMs good";
+ $this->logger->info( $msg );
+ $this->debugCallback( $msg );
}
$response['jobs'][] = array(
@@ -172,10 +226,15 @@ class JobRunner {
break;
}
- // Don't let any of the main DB slaves get backed up
+ // Don't let any of the main DB slaves get backed up.
+ // This only waits for so long before exiting and letting
+ // other wikis in the farm (on different masters) get a chance.
$timePassed = microtime( true ) - $lastTime;
if ( $timePassed >= 5 || $timePassed < 0 ) {
- wfWaitForSlaves( $lastTime );
+ if ( !wfWaitForSlaves( $lastTime, false, '*', 5 ) ) {
+ $response['reached'] = 'slave-lag-limit';
+ break;
+ }
$lastTime = microtime( true );
}
// Don't let any queue slaves/backups fall behind
@@ -239,7 +298,6 @@ class JobRunner {
* @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 ) ) {
@@ -278,7 +336,6 @@ class JobRunner {
* @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 );
@@ -341,10 +398,9 @@ class JobRunner {
* Log the job message
* @param string $msg The message to log
*/
- private function runJobsLog( $msg ) {
+ private function debugCallback( $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
index 9fa7747f..42d2a39b 100644
--- a/includes/jobqueue/JobSpecification.php
+++ b/includes/jobqueue/JobSpecification.php
@@ -91,8 +91,8 @@ class JobSpecification implements IJobSpecification {
/** @var Title */
protected $title;
- /** @var bool Expensive jobs may set this to true */
- protected $ignoreDuplicates;
+ /** @var array */
+ protected $opts;
/**
* @param string $type
@@ -104,11 +104,12 @@ class JobSpecification implements IJobSpecification {
$type, array $params, array $opts = array(), Title $title = null
) {
$this->validateParams( $params );
+ $this->validateParams( $opts );
$this->type = $type;
$this->params = $params;
$this->title = $title ?: Title::newMainPage();
- $this->ignoreDuplicates = !empty( $opts['removeDuplicates'] );
+ $this->opts = $opts;
}
/**
@@ -158,7 +159,7 @@ class JobSpecification implements IJobSpecification {
* @return bool Whether only one of each identical set of jobs should be run
*/
public function ignoreDuplicates() {
- return $this->ignoreDuplicates;
+ return !empty( $this->opts['removeDuplicates'] );
}
/**
@@ -186,4 +187,31 @@ class JobSpecification implements IJobSpecification {
return $info;
}
+
+ /**
+ * @return array Field/value map that can immediately be serialized
+ * @since 1.25
+ */
+ public function toSerializableArray() {
+ return array(
+ 'type' => $this->type,
+ 'params' => $this->params,
+ 'opts' => $this->opts,
+ 'title' => array(
+ 'ns' => $this->title->getNamespace(),
+ 'key' => $this->title->getDbKey()
+ )
+ );
+ }
+
+ /**
+ * @param array $map Field/value map
+ * @return JobSpecification
+ * @since 1.25
+ */
+ public static function newFromArray( array $map ) {
+ $title = Title::makeTitle( $map['title']['ns'], $map['title']['key'] );
+
+ return new self( $map['type'], $map['params'], $map['opts'], $title );
+ }
}
diff --git a/includes/jobqueue/aggregator/JobQueueAggregator.php b/includes/jobqueue/aggregator/JobQueueAggregator.php
index 8600eed9..febc277a 100644
--- a/includes/jobqueue/aggregator/JobQueueAggregator.php
+++ b/includes/jobqueue/aggregator/JobQueueAggregator.php
@@ -34,7 +34,7 @@ abstract class JobQueueAggregator {
/**
* @param array $params
*/
- protected function __construct( array $params ) {
+ public function __construct( array $params ) {
}
/**
@@ -73,9 +73,7 @@ abstract class JobQueueAggregator {
* @return bool Success
*/
final public function notifyQueueEmpty( $wiki, $type ) {
- wfProfileIn( __METHOD__ );
$ok = $this->doNotifyQueueEmpty( $wiki, $type );
- wfProfileOut( __METHOD__ );
return $ok;
}
@@ -93,9 +91,7 @@ abstract class JobQueueAggregator {
* @return bool Success
*/
final public function notifyQueueNonEmpty( $wiki, $type ) {
- wfProfileIn( __METHOD__ );
$ok = $this->doNotifyQueueNonEmpty( $wiki, $type );
- wfProfileOut( __METHOD__ );
return $ok;
}
@@ -111,9 +107,7 @@ abstract class JobQueueAggregator {
* @return array (job type => (list of wiki IDs))
*/
final public function getAllReadyWikiQueues() {
- wfProfileIn( __METHOD__ );
$res = $this->doGetAllReadyWikiQueues();
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -129,9 +123,7 @@ abstract class JobQueueAggregator {
* @return bool Success
*/
final public function purge() {
- wfProfileIn( __METHOD__ );
$res = $this->doPurge();
- wfProfileOut( __METHOD__ );
return $res;
}
@@ -160,3 +152,21 @@ abstract class JobQueueAggregator {
return $pendingDBs;
}
}
+
+class JobQueueAggregatorNull extends JobQueueAggregator {
+ protected function doNotifyQueueEmpty( $wiki, $type ) {
+ return true;
+ }
+
+ protected function doNotifyQueueNonEmpty( $wiki, $type ) {
+ return true;
+ }
+
+ protected function doGetAllReadyWikiQueues() {
+ return array();
+ }
+
+ protected function doPurge() {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/includes/jobqueue/aggregator/JobQueueAggregatorMemc.php b/includes/jobqueue/aggregator/JobQueueAggregatorMemc.php
deleted file mode 100644
index ae266ef3..00000000
--- a/includes/jobqueue/aggregator/JobQueueAggregatorMemc.php
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-/**
- * Job queue aggregator code that uses BagOStuff.
- *
- * This 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
- */
-
-/**
- * Class to handle tracking information about all queues using BagOStuff
- *
- * @ingroup JobQueue
- * @since 1.21
- */
-class JobQueueAggregatorMemc extends JobQueueAggregator {
- /** @var BagOStuff */
- protected $cache;
-
- protected $cacheTTL; // integer; seconds
-
- /**
- * @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.
- */
- protected function __construct( array $params ) {
- parent::__construct( $params );
- $this->cache = isset( $params['objectCache'] )
- ? wfGetCache( $params['objectCache'] )
- : wfGetMainCache();
- $this->cacheTTL = isset( $params['cacheTTL'] ) ? $params['cacheTTL'] : 180; // 3 min
- }
-
- /**
- * @see JobQueueAggregator::doNotifyQueueEmpty()
- */
- protected function doNotifyQueueEmpty( $wiki, $type ) {
- $key = $this->getReadyQueueCacheKey();
- // Delist the queue from the "ready queue" list
- if ( $this->cache->add( "$key:lock", 1, 60 ) ) { // lock
- $curInfo = $this->cache->get( $key );
- if ( is_array( $curInfo ) && isset( $curInfo['pendingDBs'][$type] ) ) {
- if ( in_array( $wiki, $curInfo['pendingDBs'][$type] ) ) {
- $curInfo['pendingDBs'][$type] = array_diff(
- $curInfo['pendingDBs'][$type], array( $wiki ) );
- $this->cache->set( $key, $curInfo );
- }
- }
- $this->cache->delete( "$key:lock" ); // unlock
- }
-
- return true;
- }
-
- /**
- * @see JobQueueAggregator::doNotifyQueueNonEmpty()
- */
- protected function doNotifyQueueNonEmpty( $wiki, $type ) {
- return true; // updated periodically
- }
-
- /**
- * @see JobQueueAggregator::doAllGetReadyWikiQueues()
- */
- protected function doGetAllReadyWikiQueues() {
- $key = $this->getReadyQueueCacheKey();
- // If the cache entry wasn't present, is stale, or in .1% of cases otherwise,
- // regenerate the cache. Use any available stale cache if another process is
- // currently regenerating the pending DB information.
- $pendingDbInfo = $this->cache->get( $key );
- if ( !is_array( $pendingDbInfo )
- || ( time() - $pendingDbInfo['timestamp'] ) > $this->cacheTTL
- || mt_rand( 0, 999 ) == 0
- ) {
- if ( $this->cache->add( "$key:rebuild", 1, 1800 ) ) { // lock
- $pendingDbInfo = array(
- 'pendingDBs' => $this->findPendingWikiQueues(),
- 'timestamp' => time()
- );
- for ( $attempts = 1; $attempts <= 25; ++$attempts ) {
- if ( $this->cache->add( "$key:lock", 1, 60 ) ) { // lock
- $this->cache->set( $key, $pendingDbInfo );
- $this->cache->delete( "$key:lock" ); // unlock
- break;
- }
- }
- $this->cache->delete( "$key:rebuild" ); // unlock
- }
- }
-
- return is_array( $pendingDbInfo )
- ? $pendingDbInfo['pendingDBs']
- : array(); // cache is both empty and locked
- }
-
- /**
- * @see JobQueueAggregator::doPurge()
- */
- protected function doPurge() {
- return $this->cache->delete( $this->getReadyQueueCacheKey() );
- }
-
- /**
- * @return string
- */
- private function getReadyQueueCacheKey() {
- return "jobqueue:aggregator:ready-queues:v1"; // global
- }
-}
diff --git a/includes/jobqueue/aggregator/JobQueueAggregatorRedis.php b/includes/jobqueue/aggregator/JobQueueAggregatorRedis.php
index db9e764c..847dd6f4 100644
--- a/includes/jobqueue/aggregator/JobQueueAggregatorRedis.php
+++ b/includes/jobqueue/aggregator/JobQueueAggregatorRedis.php
@@ -44,7 +44,7 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
* If a hostname is specified but no port, the standard port number
* 6379 will be used. Required.
*/
- protected function __construct( array $params ) {
+ public function __construct( array $params ) {
parent::__construct( $params );
$this->servers = isset( $params['redisServers'] )
? $params['redisServers']
diff --git a/includes/jobqueue/jobs/AssembleUploadChunksJob.php b/includes/jobqueue/jobs/AssembleUploadChunksJob.php
index 9e9bda6f..b7f09e77 100644
--- a/includes/jobqueue/jobs/AssembleUploadChunksJob.php
+++ b/includes/jobqueue/jobs/AssembleUploadChunksJob.php
@@ -35,26 +35,16 @@ class AssembleUploadChunksJob extends Job {
public function run() {
$scope = RequestContext::importScopedSession( $this->params['session'] );
$context = RequestContext::getMain();
+ $user = $context->getUser();
try {
- $user = $context->getUser();
if ( !$user->isLoggedIn() ) {
$this->setLastError( "Could not load the author user from session." );
return false;
}
- if ( count( $_SESSION ) === 0 ) {
- // Empty session probably indicates that we didn't associate
- // 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" );
-
- return false;
- }
-
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Poll', 'stage' => 'assembling', 'status' => Status::newGood() )
);
@@ -70,6 +60,7 @@ class AssembleUploadChunksJob extends Job {
$status = $upload->concatenateChunks();
if ( !$status->isGood() ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status )
);
@@ -93,6 +84,7 @@ class AssembleUploadChunksJob extends Job {
// Cache the info so the user doesn't have to wait forever to get the final info
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Success',
@@ -102,8 +94,9 @@ class AssembleUploadChunksJob extends Job {
'status' => Status::newGood()
)
);
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Failure',
diff --git a/includes/jobqueue/jobs/DuplicateJob.php b/includes/jobqueue/jobs/DuplicateJob.php
index 1fa6cefe..c5e3a234 100644
--- a/includes/jobqueue/jobs/DuplicateJob.php
+++ b/includes/jobqueue/jobs/DuplicateJob.php
@@ -18,7 +18,7 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup Cache
+ * @ingroup JobQueue
*/
/**
diff --git a/includes/jobqueue/jobs/EnqueueJob.php b/includes/jobqueue/jobs/EnqueueJob.php
new file mode 100644
index 00000000..46fb2aa7
--- /dev/null
+++ b/includes/jobqueue/jobs/EnqueueJob.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Router job that takes jobs and enqueues them.
+ *
+ * This 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
+ */
+
+/**
+ * Router job that takes jobs and enqueues them to their proper queues
+ *
+ * This can be used for several things:
+ * - a) Making multi-job enqueues more robust by atomically enqueueing
+ * a single job that pushes the actual jobs (with retry logic)
+ * - b) Masking the latency of pushing jobs to different queues/wikis
+ * - c) Low-latency enqueues to push jobs from warm to hot datacenters
+ *
+ * @ingroup JobQueue
+ * @since 1.25
+ */
+final class EnqueueJob extends Job {
+ /**
+ * Callers should use the factory methods instead
+ *
+ * @param Title $title
+ * @param array $params Job parameters
+ */
+ function __construct( $title, $params ) {
+ parent::__construct( 'enqueue', $title, $params );
+ }
+
+ /**
+ * @param Job|JobSpecification|array $jobs
+ * @return JobRouteJob
+ */
+ public static function newFromLocalJobs( $jobs ) {
+ $jobs = is_array( $jobs ) ? $jobs : array( $jobs );
+
+ return self::newFromJobsByWiki( array( wfWikiID() => $jobs ) );
+ }
+
+ /**
+ * @param array $jobsByWiki Map of (wiki => JobSpecification list)
+ * @return JobRouteJob
+ */
+ public static function newFromJobsByWiki( array $jobsByWiki ) {
+ $jobMapsByWiki = array();
+ foreach ( $jobsByWiki as $wiki => $jobs ) {
+ $jobMapsByWiki[$wiki] = array();
+ foreach ( $jobs as $job ) {
+ if ( $job instanceof JobSpecification ) {
+ $jobMapsByWiki[$wiki][] = $job->toSerializableArray();
+ } else {
+ throw new InvalidArgumentException( "Jobs must be of type JobSpecification." );
+ }
+ }
+ }
+
+ return new self( Title::newMainPage(), array( 'jobsByWiki' => $jobMapsByWiki ) );
+ }
+
+ public function run() {
+ foreach ( $this->params['jobsByWiki'] as $wiki => $jobMaps ) {
+ $jobSpecs = array();
+ foreach ( $jobMaps as $jobMap ) {
+ $jobSpecs[] = JobSpecification::newFromArray( $jobMap );
+ }
+ JobQueueGroup::singleton( $wiki )->push( $jobSpecs );
+ }
+
+ return true;
+ }
+}
diff --git a/includes/jobqueue/jobs/HTMLCacheUpdateJob.php b/includes/jobqueue/jobs/HTMLCacheUpdateJob.php
index 4d1e72c9..e5e521c3 100644
--- a/includes/jobqueue/jobs/HTMLCacheUpdateJob.php
+++ b/includes/jobqueue/jobs/HTMLCacheUpdateJob.php
@@ -18,6 +18,7 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
+ * @ingroup JobQueue
* @ingroup Cache
*/
@@ -26,9 +27,9 @@
*
* 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.
+ * These jobs 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.
+ * These jobs have (pages:(<page ID>:(<namespace>,<title>),...) set.
*
* @ingroup JobQueue
*/
@@ -42,17 +43,8 @@ class HTMLCacheUpdateJob extends Job {
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
- }
+ if ( isset( $this->params['table'] ) && !isset( $this->params['pages'] ) ) {
+ $this->params['recursive'] = true; // b/c; base job
}
// Job to purge all (or a range of) backlink pages for a page
@@ -67,29 +59,15 @@ class HTMLCacheUpdateJob extends Job {
array( 'params' => $this->getRootJobParams() )
);
JobQueueGroup::singleton()->push( $jobs );
- // Job to purge pages for for a set of titles
+ // Job to purge pages 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 );
+ // Job to update a single title
+ } else {
+ $t = $this->title;
+ $this->invalidateTitles( array(
+ $t->getArticleID() => array( $t->getNamespace(), $t->getDBkey() )
+ ) );
}
return true;
diff --git a/includes/jobqueue/jobs/NullJob.php b/includes/jobqueue/jobs/NullJob.php
index 66291e9d..f94d6ebc 100644
--- a/includes/jobqueue/jobs/NullJob.php
+++ b/includes/jobqueue/jobs/NullJob.php
@@ -18,7 +18,7 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @ingroup Cache
+ * @ingroup JobQueue
*/
/**
diff --git a/includes/jobqueue/jobs/PublishStashedFileJob.php b/includes/jobqueue/jobs/PublishStashedFileJob.php
index 918a392d..a922dd3d 100644
--- a/includes/jobqueue/jobs/PublishStashedFileJob.php
+++ b/includes/jobqueue/jobs/PublishStashedFileJob.php
@@ -19,12 +19,14 @@
*
* @file
* @ingroup Upload
+ * @ingroup JobQueue
*/
/**
* Upload a file from the upload stash into the local file repo.
*
* @ingroup Upload
+ * @ingroup JobQueue
*/
class PublishStashedFileJob extends Job {
public function __construct( $title, $params ) {
@@ -35,26 +37,16 @@ class PublishStashedFileJob extends Job {
public function run() {
$scope = RequestContext::importScopedSession( $this->params['session'] );
$context = RequestContext::getMain();
+ $user = $context->getUser();
try {
- $user = $context->getUser();
if ( !$user->isLoggedIn() ) {
$this->setLastError( "Could not load the author user from session." );
return false;
}
- if ( count( $_SESSION ) === 0 ) {
- // Empty session probably indicates that we didn't associate
- // 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" );
-
- return false;
- }
-
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Poll', 'stage' => 'publish', 'status' => Status::newGood() )
);
@@ -72,6 +64,7 @@ class PublishStashedFileJob extends Job {
$status = Status::newFatal( 'verification-error' );
$status->value = array( 'verification' => $verification );
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
);
@@ -89,6 +82,7 @@ class PublishStashedFileJob extends Job {
);
if ( !$status->isGood() ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array( 'result' => 'Failure', 'stage' => 'publish', 'status' => $status )
);
@@ -106,6 +100,7 @@ class PublishStashedFileJob extends Job {
// Cache the info so the user doesn't have to wait forever to get the final info
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Success',
@@ -115,8 +110,9 @@ class PublishStashedFileJob extends Job {
'status' => Status::newGood()
)
);
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
UploadBase::setSessionStatus(
+ $user,
$this->params['filekey'],
array(
'result' => 'Failure',
diff --git a/includes/jobqueue/jobs/RecentChangesUpdateJob.php b/includes/jobqueue/jobs/RecentChangesUpdateJob.php
new file mode 100644
index 00000000..cc04595d
--- /dev/null
+++ b/includes/jobqueue/jobs/RecentChangesUpdateJob.php
@@ -0,0 +1,223 @@
+<?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
+ * @ingroup JobQueue
+ */
+
+/**
+ * Job for pruning recent changes
+ *
+ * @ingroup JobQueue
+ * @since 1.25
+ */
+class RecentChangesUpdateJob extends Job {
+ function __construct( $title, $params ) {
+ parent::__construct( 'recentChangesUpdate', $title, $params );
+
+ if ( !isset( $params['type'] ) ) {
+ throw new Exception( "Missing 'type' parameter." );
+ }
+
+ $this->removeDuplicates = true;
+ }
+
+ /**
+ * @return RecentChangesUpdateJob
+ */
+ final public static function newPurgeJob() {
+ return new self(
+ SpecialPage::getTitleFor( 'Recentchanges' ), array( 'type' => 'purge' )
+ );
+ }
+
+ /**
+ * @return RecentChangesUpdateJob
+ * @since 1.26
+ */
+ final public static function newCacheUpdateJob() {
+ return new self(
+ SpecialPage::getTitleFor( 'Recentchanges' ), array( 'type' => 'cacheUpdate' )
+ );
+ }
+
+ public function run() {
+ if ( $this->params['type'] === 'purge' ) {
+ $this->purgeExpiredRows();
+ } elseif ( $this->params['type'] === 'cacheUpdate' ) {
+ $this->updateActiveUsers();
+ } else {
+ throw new InvalidArgumentException(
+ "Invalid 'type' parameter '{$this->params['type']}'." );
+ }
+
+ return true;
+ }
+
+ protected function purgeExpiredRows() {
+ global $wgRCMaxAge;
+
+ $lockKey = wfWikiID() . ':recentchanges-prune';
+
+ $dbw = wfGetDB( DB_MASTER );
+ if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
+ return; // already in progress
+ }
+ $batchSize = 100; // Avoid slave lag
+
+ $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
+ do {
+ $rcIds = $dbw->selectFieldValues( 'recentchanges',
+ 'rc_id',
+ array( 'rc_timestamp < ' . $dbw->addQuotes( $cutoff ) ),
+ __METHOD__,
+ array( 'LIMIT' => $batchSize )
+ );
+ if ( $rcIds ) {
+ $dbw->delete( 'recentchanges', array( 'rc_id' => $rcIds ), __METHOD__ );
+ }
+ // Commit in chunks to avoid slave lag
+ $dbw->commit( __METHOD__, 'flush' );
+
+ if ( count( $rcIds ) === $batchSize ) {
+ // There might be more, so try waiting for slaves
+ if ( !wfWaitForSlaves( null, false, false, /* $timeout = */ 3 ) ) {
+ // Another job will continue anyway
+ break;
+ }
+ }
+ } while ( $rcIds );
+
+ $dbw->unlock( $lockKey, __METHOD__ );
+ }
+
+ protected function updateActiveUsers() {
+ global $wgActiveUserDays;
+
+ // Users that made edits at least this many days ago are "active"
+ $days = $wgActiveUserDays;
+ // Pull in the full window of active users in this update
+ $window = $wgActiveUserDays * 86400;
+
+ $dbw = wfGetDB( DB_MASTER );
+ // JobRunner uses DBO_TRX, but doesn't call begin/commit itself;
+ // onTransactionIdle() will run immediately since there is no trx.
+ $dbw->onTransactionIdle( function() use ( $dbw, $days, $window ) {
+ // Avoid disconnect/ping() cycle that makes locks fall off
+ $dbw->setSessionOptions( array( 'connTimeout' => 900 ) );
+
+ $lockKey = wfWikiID() . '-activeusers';
+ if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
+ return false; // exclusive update (avoids duplicate entries)
+ }
+
+ $nowUnix = time();
+ // Get the last-updated timestamp for the cache
+ $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, $nowUnix - $days * 86400 );
+ $eTimestamp = min( $sTimestamp + $window, $nowUnix );
+
+ // 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( $nowUnix - $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
+ 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__ );
+ wfWaitForSlaves();
+ }
+ }
+
+ // If a transaction was already started, it might have an old
+ // snapshot, so kludge the timestamp range back as needed.
+ $asOfTimestamp = min( $eTimestamp, (int)$dbw->trxTimestamp() );
+
+ // Touch the data freshness timestamp
+ $dbw->replace( 'querycache_info',
+ array( 'qci_type' ),
+ array( 'qci_type' => 'activeusers',
+ 'qci_timestamp' => $dbw->timestamp( $asOfTimestamp ) ), // not always $now
+ __METHOD__
+ );
+
+ $dbw->unlock( $lockKey, __METHOD__ );
+ } );
+ }
+}
diff --git a/includes/jobqueue/jobs/RefreshLinksJob.php b/includes/jobqueue/jobs/RefreshLinksJob.php
index f82af273..1252b0b5 100644
--- a/includes/jobqueue/jobs/RefreshLinksJob.php
+++ b/includes/jobqueue/jobs/RefreshLinksJob.php
@@ -26,9 +26,9 @@
*
* 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.
+ * These jobs 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.
+ * These jobs 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.
*
@@ -39,6 +39,10 @@ class RefreshLinksJob extends Job {
function __construct( $title, $params = '' ) {
parent::__construct( 'refreshLinks', $title, $params );
+ // A separate type is used just for cascade-protected backlinks
+ if ( !empty( $this->params['prioritize'] ) ) {
+ $this->command .= 'Prioritized';
+ }
// 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)
@@ -86,7 +90,7 @@ class RefreshLinksJob extends Job {
array( 'params' => $extraParams )
);
JobQueueGroup::singleton()->push( $jobs );
- // Job to update link tables for for a set of titles
+ // Job to update link tables for a set of titles
} elseif ( isset( $this->params['pages'] ) ) {
foreach ( $this->params['pages'] as $pageId => $nsAndKey ) {
list( $ns, $dbKey ) = $nsAndKey;
@@ -100,6 +104,10 @@ class RefreshLinksJob extends Job {
return true;
}
+ /**
+ * @param Title $title
+ * @return bool
+ */
protected function runForTitle( Title $title = null ) {
$linkCache = LinkCache::singleton();
$linkCache->clear();
@@ -157,7 +165,7 @@ class RefreshLinksJob extends Job {
$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.
+ // cache as this can 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()
diff --git a/includes/jobqueue/jobs/RefreshLinksJob2.php b/includes/jobqueue/jobs/RefreshLinksJob2.php
deleted file mode 100644
index 97405aeb..00000000
--- a/includes/jobqueue/jobs/RefreshLinksJob2.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?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
- */
-
-/**
- * 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 ) {
- 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 bool Success
- */
- function run() {
- global $wgUpdateRowsPerJob;
-
- $linkCache = LinkCache::singleton();
- $linkCache->clear();
-
- if ( is_null( $this->title ) ) {
- $this->error = "refreshLinks2: Invalid title";
- return false;
- }
-
- // Back compat for pre-r94435 jobs
- $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks';
-
- // 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'] ) ) {
- $masterPos = $this->params['masterPos'];
- } elseif ( wfGetLB()->getServerCount() > 1 ) {
- $masterPos = wfGetLB()->getMasterPos();
- } else {
- $masterPos = false;
- }
-
- $tbc = $this->title->getBacklinkCache();
-
- $jobs = array(); // jobs to insert
- if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
- # This is a partition job to trigger the insertion of leaf jobs...
- $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
- } else {
- # This is a base job to trigger the insertion of partitioned jobs...
- if ( $tbc->getNumLinks( $table, $wgUpdateRowsPerJob + 1 ) <= $wgUpdateRowsPerJob ) {
- # Just directly insert the single per-title jobs
- $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
- } else {
- # Insert the partition jobs to make per-title jobs
- foreach ( $tbc->partition( $table, $wgUpdateRowsPerJob ) as $batch ) {
- list( $start, $end ) = $batch;
- $jobs[] = new RefreshLinksJob2( $this->title,
- array(
- 'table' => $table,
- 'start' => $start,
- 'end' => $end,
- 'masterPos' => $masterPos,
- ) + $this->getRootJobParams() // carry over information for de-duplication
- );
- }
- }
- }
-
- if ( count( $jobs ) ) {
- JobQueueGroup::singleton()->push( $jobs );
- }
-
- return true;
- }
-
- /**
- * @param string $table
- * @param mixed $masterPos
- * @return array
- */
- protected function getSingleTitleJobs( $table, $masterPos ) {
- # The "start"/"end" fields are not set for the base jobs
- $start = isset( $this->params['start'] ) ? $this->params['start'] : false;
- $end = isset( $this->params['end'] ) ? $this->params['end'] : false;
- $titles = $this->title->getBacklinkCache()->getLinks( $table, $start, $end );
- # Convert into single page refresh links jobs.
- # This handles well when in sapi mode and is useful in any case for job
- # de-duplication. If many pages use template A, and that template itself
- # uses template B, then an edit to both will create many duplicate jobs.
- # Roughly speaking, for each page, one of the "RefreshLinksJob" jobs will
- # get run first, and when it does, it will remove the duplicates. Of course,
- # one page could have its job popped when the other page's job is still
- # buried within the logic of a refreshLinks2 job.
- $jobs = array();
- foreach ( $titles as $title ) {
- $jobs[] = new RefreshLinksJob( $title,
- array( 'masterPos' => $masterPos ) + $this->getRootJobParams()
- ); // carry over information for de-duplication
- }
- return $jobs;
- }
-
- /**
- * @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;
- }
-}
diff --git a/includes/jobqueue/jobs/ThumbnailRenderJob.php b/includes/jobqueue/jobs/ThumbnailRenderJob.php
new file mode 100644
index 00000000..ab381388
--- /dev/null
+++ b/includes/jobqueue/jobs/ThumbnailRenderJob.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * Job for asynchronous rendering of thumbnails.
+ *
+ * This 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 for asynchronous rendering of thumbnails.
+ *
+ * @ingroup JobQueue
+ */
+class ThumbnailRenderJob extends Job {
+ public function __construct( $title, $params ) {
+ parent::__construct( 'ThumbnailRender', $title, $params );
+ }
+
+ public function run() {
+ global $wgUploadThumbnailRenderMethod;
+
+ $transformParams = $this->params['transformParams'];
+
+ $file = wfLocalFile( $this->title );
+ $file->load( File::READ_LATEST );
+
+ if ( $file && $file->exists() ) {
+ if ( $wgUploadThumbnailRenderMethod === 'jobqueue' ) {
+ $thumb = $file->transform( $transformParams, File::RENDER_NOW );
+
+ if ( $thumb && !$thumb->isError() ) {
+ return true;
+ } else {
+ $this->setLastError( __METHOD__ . ': thumbnail couln\'t be generated' );
+ return false;
+ }
+ } elseif ( $wgUploadThumbnailRenderMethod === 'http' ) {
+ $status = $this->hitThumbUrl( $file, $transformParams );
+
+ wfDebug( __METHOD__ . ": received status {$status}\n" );
+
+ if ( $status === 200 || $status === 301 || $status === 302 ) {
+ return true;
+ } elseif ( $status ) {
+ // Note that this currently happens (500) when requesting sizes larger then or
+ // equal to the original, which is harmless.
+ $this->setLastError( __METHOD__ . ': incorrect HTTP status ' . $status );
+ return false;
+ } else {
+ $this->setLastError( __METHOD__ . ': HTTP request failure' );
+ return false;
+ }
+ } else {
+ $this->setLastError( __METHOD__ . ': unknown thumbnail render method ' . $wgUploadThumbnailRenderMethod );
+ return false;
+ }
+ } else {
+ $this->setLastError( __METHOD__ . ': file doesn\'t exist' );
+ return false;
+ }
+ }
+
+ protected function hitThumbUrl( $file, $transformParams ) {
+ global $wgUploadThumbnailRenderHttpCustomHost, $wgUploadThumbnailRenderHttpCustomDomain;
+
+ $thumbName = $file->thumbName( $transformParams );
+ $thumbUrl = $file->getThumbUrl( $thumbName );
+
+ if ( $wgUploadThumbnailRenderHttpCustomDomain ) {
+ $parsedUrl = wfParseUrl( $thumbUrl );
+
+ if ( !$parsedUrl || !isset( $parsedUrl['path'] ) || !strlen( $parsedUrl['path'] ) ) {
+ return false;
+ }
+
+ $thumbUrl = '//' . $wgUploadThumbnailRenderHttpCustomDomain . $parsedUrl['path'];
+ }
+
+ wfDebug( __METHOD__ . ": hitting url {$thumbUrl}\n" );
+
+ $request = MWHttpRequest::factory( $thumbUrl,
+ array( 'method' => 'HEAD', 'followRedirects' => true ),
+ __METHOD__
+ );
+
+ if ( $wgUploadThumbnailRenderHttpCustomHost ) {
+ $request->setHeader( 'Host', $wgUploadThumbnailRenderHttpCustomHost );
+ }
+
+ $status = $request->execute();
+
+ return $request->getStatus();
+ }
+}
diff --git a/includes/jobqueue/jobs/UploadFromUrlJob.php b/includes/jobqueue/jobs/UploadFromUrlJob.php
index a09db15a..d15fd025 100644
--- a/includes/jobqueue/jobs/UploadFromUrlJob.php
+++ b/includes/jobqueue/jobs/UploadFromUrlJob.php
@@ -81,7 +81,7 @@ class UploadFromUrlJob extends Job {
if ( $warnings ) {
# Stash the upload
- $key = $this->upload->stashFile();
+ $key = $this->upload->stashFile( $this->user );
// @todo FIXME: This has been broken for a while.
// User::leaveUserMessage() does not exist.
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php
index f3e5c76d..095811ff 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -71,6 +71,13 @@ class FormatJson {
const TRY_FIXING = 0x200;
/**
+ * If set, strip comments from input before parsing as JSON.
+ *
+ * @since 1.25
+ */
+ const STRIP_COMMENTS = 0x400;
+
+ /**
* Regex that matches whitespace inside empty arrays and objects.
*
* This doesn't affect regular strings inside the JSON because those can't
@@ -115,7 +122,7 @@ class FormatJson {
* 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
+ * @return string|false String if successful; false upon failure
*/
public static function encode( $value, $pretty = false, $escaping = 0 ) {
if ( !is_string( $pretty ) ) {
@@ -130,8 +137,9 @@ class FormatJson {
}
/**
- * 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.
+ * 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.
@@ -147,13 +155,18 @@ class FormatJson {
/**
* Decodes a JSON string.
- * Unlike FormatJson::decode(), if $value represents null value, it will be properly decoded as valid.
+ * 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
+ * @param int $options A bit field that allows FORCE_ASSOC, TRY_FIXING,
+ * STRIP_COMMENTS
* @return Status If valid JSON, the value is available in $result->getValue()
*/
public static function parse( $value, $options = 0 ) {
+ if ( $options & self::STRIP_COMMENTS ) {
+ $value = self::stripComments( $value );
+ }
$assoc = ( $options & self::FORCE_ASSOC ) !== 0;
$result = json_decode( $value, $assoc );
$code = json_last_error();
@@ -219,7 +232,7 @@ class FormatJson {
* @param mixed $value
* @param string|bool $pretty
* @param int $escaping
- * @return string|bool
+ * @return string|false
*/
private static function encode54( $value, $pretty, $escaping ) {
static $bug66021;
@@ -271,7 +284,7 @@ class FormatJson {
* @param mixed $value
* @param string|bool $pretty
* @param int $escaping
- * @return string|bool
+ * @return string|false
*/
private static function encode53( $value, $pretty, $escaping ) {
$options = ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP );
@@ -347,4 +360,79 @@ class FormatJson {
return str_replace( "\x01", '\"', $buf );
}
+
+ /**
+ * Remove multiline and single line comments from an otherwise valid JSON
+ * input string. This can be used as a preprocessor for to allow JSON
+ * formatted configuration files to contain comments.
+ *
+ * @param string $json
+ * @return string JSON with comments removed
+ */
+ public static function stripComments( $json ) {
+ // Ensure we have a string
+ $str = (string) $json;
+ $buffer = '';
+ $maxLen = strlen( $str );
+ $mark = 0;
+
+ $inString = false;
+ $inComment = false;
+ $multiline = false;
+
+ for ($idx = 0; $idx < $maxLen; $idx++) {
+ switch ( $str[$idx] ) {
+ case '"':
+ $lookBehind = ( $idx - 1 >= 0 ) ? $str[$idx - 1] : '';
+ if ( !$inComment && $lookBehind !== '\\' ) {
+ // Either started or ended a string
+ $inString = !$inString;
+ }
+ break;
+
+ case '/':
+ $lookAhead = ( $idx + 1 < $maxLen ) ? $str[$idx + 1] : '';
+ $lookBehind = ( $idx - 1 >= 0 ) ? $str[$idx - 1] : '';
+ if ( $inString ) {
+ continue;
+
+ } elseif ( !$inComment &&
+ ( $lookAhead === '/' || $lookAhead === '*' )
+ ) {
+ // Transition into a comment
+ // Add characters seen to buffer
+ $buffer .= substr( $str, $mark, $idx - $mark );
+ // Consume the look ahead character
+ $idx++;
+ // Track state
+ $inComment = true;
+ $multiline = $lookAhead === '*';
+
+ } elseif ( $multiline && $lookBehind === '*' ) {
+ // Found the end of the current comment
+ $mark = $idx + 1;
+ $inComment = false;
+ $multiline = false;
+ }
+ break;
+
+ case "\n":
+ if ( $inComment && !$multiline ) {
+ // Found the end of the current comment
+ $mark = $idx + 1;
+ $inComment = false;
+ }
+ break;
+ }
+ }
+ if ( $inComment ) {
+ // Comment ends with input
+ // Technically we should check to ensure that we aren't in
+ // a multiline comment that hasn't been properly ended, but this
+ // is a strip filter, not a validating parser.
+ $mark = $maxLen;
+ }
+ // Add final chunk to buffer before returning
+ return $buffer . substr( $str, $mark, $maxLen - $mark );
+ }
}
diff --git a/includes/libs/APACHE-LICENSE-2.0.txt b/includes/libs/APACHE-LICENSE-2.0.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/includes/libs/APACHE-LICENSE-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/includes/utils/ArrayUtils.php b/includes/libs/ArrayUtils.php
index 1e521cb8..f9340210 100644
--- a/includes/utils/ArrayUtils.php
+++ b/includes/libs/ArrayUtils.php
@@ -98,11 +98,11 @@ class ArrayUtils {
*
* @since 1.23
*
- * @param array $valueCallback A function to call to get the value with
+ * @param callable $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
+ * @param callable $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.
*
diff --git a/includes/libs/BufferingStatsdDataFactory.php b/includes/libs/BufferingStatsdDataFactory.php
new file mode 100644
index 00000000..ea5b09dc
--- /dev/null
+++ b/includes/libs/BufferingStatsdDataFactory.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+
+/**
+ * A factory for application metric data.
+ *
+ * This class prepends a context-specific prefix to each metric key and keeps
+ * a reference to each constructed metric in an internal array buffer.
+ *
+ * @since 1.25
+ */
+class BufferingStatsdDataFactory extends StatsdDataFactory {
+ protected $buffer = array();
+
+ public function __construct( $prefix ) {
+ parent::__construct();
+ $this->prefix = $prefix;
+ }
+
+ public function produceStatsdData( $key, $value = 1, $metric = self::STATSD_METRIC_COUNT ) {
+ $this->buffer[] = $entity = $this->produceStatsdDataEntity();
+ if ( $key !== null ) {
+ $prefixedKey = ltrim( $this->prefix . '.' . $key, '.' );
+ $entity->setKey( $prefixedKey );
+ }
+ if ( $value !== null ) {
+ $entity->setValue( $value );
+ }
+ if ( $metric !== null ) {
+ $entity->setMetric( $metric );
+ }
+ return $entity;
+ }
+
+ public function getBuffer() {
+ return $this->buffer;
+ }
+}
diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php
deleted file mode 100644
index 07a83a54..00000000
--- a/includes/libs/CSSJanus.php
+++ /dev/null
@@ -1,458 +0,0 @@
-<?php
-/**
- * PHP port of CSSJanus.
- *
- * This 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 PHP port of CSSJanus, a utility that transforms CSS style sheets
- * written for LTR to RTL.
- *
- * The original Python version of CSSJanus is Copyright 2008 by Google Inc. and
- * is distributed under the Apache license. This PHP port is Copyright 2010 by
- * Roan Kattouw and is dual-licensed under the GPL (as in the comment above) and
- * the Apache (as in the original code) licenses.
- *
- * Original code: http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus.py
- * License of original code: http://code.google.com/p/cssjanus/source/browse/trunk/LICENSE
- * @author Roan Kattouw
- *
- */
-class CSSJanus {
- // Patterns defined as null are built dynamically by buildPatterns()
- private static $patterns = array(
- 'tmpToken' => '`TMP`',
- 'nonAscii' => '[\200-\377]',
- 'unicode' => '(?:(?:\\[0-9a-f]{1,6})(?:\r\n|\s)?)',
- 'num' => '(?:[0-9]*\.[0-9]+|[0-9]+)',
- 'unit' => '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)',
- 'body_selector' => 'body\s*{\s*',
- 'direction' => 'direction\s*:\s*',
- 'escape' => null,
- 'nmstart' => null,
- 'nmchar' => null,
- 'ident' => null,
- 'quantity' => null,
- 'possibly_negative_quantity' => null,
- 'color' => null,
- 'url_special_chars' => '[!#$%&*-~]',
- 'valid_after_uri_chars' => '[\'\"]?\s*',
- 'url_chars' => null,
- 'lookahead_not_open_brace' => null,
- 'lookahead_not_closing_paren' => null,
- 'lookahead_for_closing_paren' => null,
- 'lookahead_not_letter' => '(?![a-zA-Z])',
- 'lookbehind_not_letter' => '(?<![a-zA-Z])',
- 'chars_within_selector' => '[^\}]*?',
- 'noflip_annotation' => '\/\*\!?\s*@noflip\s*\*\/',
- 'noflip_single' => null,
- 'noflip_class' => null,
- 'comment' => '/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//',
- 'direction_ltr' => null,
- 'direction_rtl' => null,
- 'left' => null,
- 'right' => null,
- 'left_in_url' => null,
- 'right_in_url' => null,
- 'ltr_in_url' => null,
- 'rtl_in_url' => null,
- 'cursor_east' => null,
- 'cursor_west' => null,
- 'four_notation_quantity' => null,
- 'four_notation_color' => null,
- 'border_radius' => null,
- 'box_shadow' => null,
- 'text_shadow1' => null,
- 'text_shadow2' => null,
- 'bg_horizontal_percentage' => null,
- 'bg_horizontal_percentage_x' => null,
- );
-
- /**
- * Build patterns we can't define above because they depend on other patterns.
- */
- private static function buildPatterns() {
- 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']})";
- $patterns['nmchar'] = "(?:[_a-z0-9-]|{$patterns['nonAscii']}|{$patterns['escape']})";
- $patterns['ident'] = "-?{$patterns['nmstart']}{$patterns['nmchar']}*";
- $patterns['quantity'] = "{$patterns['num']}(?:\s*{$patterns['unit']}|{$patterns['ident']})?";
- $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_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";
- $patterns['noflip_class'] = "/({$patterns['noflip_annotation']}{$patterns['chars_within_selector']}})/i";
- $patterns['direction_ltr'] = "/({$patterns['direction']})ltr/i";
- $patterns['direction_rtl'] = "/({$patterns['direction']})rtl/i";
- $patterns['left'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
- $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
- $patterns['left_in_url'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_for_closing_paren']}/i";
- $patterns['right_in_url'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_for_closing_paren']}/i";
- $patterns['ltr_in_url'] = "/{$patterns['lookbehind_not_letter']}(ltr){$patterns['lookahead_for_closing_paren']}/i";
- $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_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";
- $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
-
- }
-
- /**
- * Transform an LTR stylesheet to RTL
- * @param string $css stylesheet to transform
- * @param $swapLtrRtlInURL Boolean: If true, swap 'ltr' and 'rtl' in URLs
- * @param $swapLeftRightInURL Boolean: If true, swap 'left' and 'right' in URLs
- * @return string Transformed stylesheet
- */
- 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);
-
- self::buildPatterns();
-
- // Tokenize single line rules with /* @noflip */
- $noFlipSingle = new CSSJanusTokenizer(self::$patterns['noflip_single'], '`NOFLIP_SINGLE`');
- $css = $noFlipSingle->tokenize($css);
-
- // Tokenize class rules with /* @noflip */
- $noFlipClass = new CSSJanusTokenizer(self::$patterns['noflip_class'], '`NOFLIP_CLASS`');
- $css = $noFlipClass->tokenize($css);
-
- // Tokenize comments
- $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);
- }
-
- 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);
-
- // Detokenize stuff we tokenized before
- $css = $comments->detokenize($css);
- $css = $noFlipClass->detokenize($css);
- $css = $noFlipSingle->detokenize($css);
-
- return $css;
- }
-
- /**
- * Replace direction: ltr; with direction: rtl; and vice versa.
- *
- * The original implementation only does this inside body selectors
- * and misses "body\n{\ndirection:ltr;\n}". This function does not have
- * these problems.
- *
- * 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);
-
- return $css;
- }
-
- /**
- * Replace 'ltr' with 'rtl' and vice versa in background URLs
- * @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);
-
- return $css;
- }
-
- /**
- * Replace 'left' with 'right' and vice versa in background URLs
- * @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);
-
- return $css;
- }
-
- /**
- * Flip rules like left: , padding-right: , etc.
- * @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);
-
- return $css;
- }
-
- /**
- * Flip East and West in rules like cursor: nw-resize;
- * @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);
-
- return $css;
- }
-
- /**
- * Swap the second and fourth parts in four-part notation rules like
- * padding: 1px 2px 3px 4px;
- *
- * Unlike the original implementation, this function doesn't suffer from
- * 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 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);
- return $css;
- }
-
- /**
- * Swaps appropriate corners in border-radius values.
- *
- * @param $css string
- * @return string
- */
- 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;
- }
-
- /**
- * Negates horizontal offset in box-shadow and text-shadow rules.
- *
- * @param $css string
- * @return string
- */
- 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) {
- // Don't mangle zeroes
- if (floatval($cssValue) === 0.0) {
- return $cssValue;
- } 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['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);
-
- return $css;
- }
-
- /**
- * Flip horizontal background percentages.
- * @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) {
- // 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) {
- $css = $replaced;
- }
-
- return $css;
- }
-
- /**
- * Callback for fixBackgroundPosition()
- * @param $matches array
- * @return string
- */
- 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;
- }
-}
-
-/**
- * Utility class used by CSSJanus that tokenizes and untokenizes things we want
- * to protect from being janused.
- * @author Roan Kattouw
- */
-class CSSJanusTokenizer {
- private $regex;
- private $token;
- private $originals;
-
- /**
- * Constructor
- * @param string $regex Regular expression whose matches to replace by a token.
- * @param string $token Token
- */
- public function __construct($regex, $token) {
- $this->regex = $regex;
- $this->token = $token;
- $this->originals = array();
- }
-
- /**
- * Replace all occurrences of $regex in $str with a token and remember
- * the original strings.
- * @param string $str to tokenize
- * @return string Tokenized string
- */
- public function tokenize($str) {
- return preg_replace_callback($this->regex, array($this, 'tokenizeCallback'), $str);
- }
-
- /**
- * @param $matches array
- * @return string
- */
- private function tokenizeCallback($matches) {
- $this->originals[] = $matches[0];
- return $this->token;
- }
-
- /**
- * Replace tokens with their originals. If multiple strings were tokenized, it's important they be
- * detokenized in exactly the SAME ORDER.
- * @param string $str previously run through tokenize()
- * @return string Original string
- */
- 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
- );
- }
-
- /**
- * @param $matches
- * @return mixed
- */
- 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 c69e79f5..ffe26a96 100644
--- a/includes/libs/CSSMin.php
+++ b/includes/libs/CSSMin.php
@@ -32,12 +32,9 @@ class CSSMin {
/* Constants */
/**
- * Maximum file size to still qualify for in-line embedding as a data-URI
- *
- * 24,576 is used because Internet Explorer has a 32,768 byte limit for data URIs,
- * which when base64 encoded will result in a 1/3 increase in size.
+ * Internet Explorer data URI length limit. See encodeImageAsDataURI().
*/
- const EMBED_SIZE_LIMIT = 24576;
+ const DATA_URI_SIZE_LIMIT = 32768;
const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*?)(?P<query>\?[^\)\'"]*?|)[\'"]?\s*\)';
const EMBED_REGEX = '\/\*\s*\@embed\s*\*\/';
const COMMENT_REGEX = '\/\*.*?\*\/';
@@ -100,33 +97,68 @@ class CSSMin {
}
/**
- * Encode an image file as a base64 data URI.
- * If the image file has a suitable MIME type and size, encode it as a
- * base64 data URI. Return false if the image type is unfamiliar or exceeds
- * the size limit.
+ * Encode an image file as a data URI.
+ *
+ * If the image file has a suitable MIME type and size, encode it as a data URI, base64-encoded
+ * for binary files or just percent-encoded otherwise. Return false if the image type is
+ * unfamiliar or file exceeds the size limit.
*
* @param string $file Image file to encode.
* @param string|null $type File's MIME type or null. If null, CSSMin will
* try to autodetect the type.
- * @param int|bool $sizeLimit If the size of the target file is greater than
- * this value, decline to encode the image file and return false
- * instead. If $sizeLimit is false, no limit is enforced.
- * @return string|bool: Image contents encoded as a data URI or false.
+ * @param bool $ie8Compat By default, a data URI will only be produced if it can be made short
+ * enough to fit in Internet Explorer 8 (and earlier) URI length limit (32,768 bytes). Pass
+ * `false` to remove this limitation.
+ * @return string|bool Image contents encoded as a data URI or false.
*/
- public static function encodeImageAsDataURI( $file, $type = null,
- $sizeLimit = self::EMBED_SIZE_LIMIT
- ) {
- if ( $sizeLimit !== false && filesize( $file ) >= $sizeLimit ) {
+ public static function encodeImageAsDataURI( $file, $type = null, $ie8Compat = true ) {
+ // Fast-fail for files that definitely exceed the maximum data URI length
+ if ( $ie8Compat && filesize( $file ) >= self::DATA_URI_SIZE_LIMIT ) {
return false;
}
+
if ( $type === null ) {
$type = self::getMimeType( $file );
}
if ( !$type ) {
return false;
}
- $data = base64_encode( file_get_contents( $file ) );
- return 'data:' . $type . ';base64,' . $data;
+
+ return self::encodeStringAsDataURI( file_get_contents( $file ), $type, $ie8Compat );
+ }
+
+ /**
+ * Encode file contents as a data URI with chosen MIME type.
+ *
+ * The URI will be base64-encoded for binary files or just percent-encoded otherwise.
+ *
+ * @since 1.25
+ *
+ * @param string $contents File contents to encode.
+ * @param string $type File's MIME type.
+ * @param bool $ie8Compat See encodeImageAsDataURI().
+ * @return string|bool Image contents encoded as a data URI or false.
+ */
+ public static function encodeStringAsDataURI( $contents, $type, $ie8Compat = true ) {
+ // Try #1: Non-encoded data URI
+ // The regular expression matches ASCII whitespace and printable characters.
+ if ( preg_match( '/^[\r\n\t\x20-\x7e]+$/', $contents ) ) {
+ // Do not base64-encode non-binary files (sane SVGs).
+ // (This often produces longer URLs, but they compress better, yielding a net smaller size.)
+ $uri = 'data:' . $type . ',' . rawurlencode( $contents );
+ if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
+ return $uri;
+ }
+ }
+
+ // Try #2: Encoded data URI
+ $uri = 'data:' . $type . ';base64,' . base64_encode( $contents );
+ if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
+ return $uri;
+ }
+
+ // A data URI couldn't be produced
+ return false;
}
/**
@@ -248,9 +280,12 @@ class CSSMin {
);
if ( $embedData ) {
+ // Remember the occurring MIME types to avoid fallbacks when embedding some files.
+ $mimeTypes = array();
+
$ruleWithEmbedded = preg_replace_callback(
$pattern,
- function ( $match ) use ( $embedAll, $local, $remote ) {
+ function ( $match ) use ( $embedAll, $local, $remote, &$mimeTypes ) {
$embed = $embedAll || $match['embed'];
$embedded = CSSMin::remapOne(
$match['file'],
@@ -260,21 +295,35 @@ class CSSMin {
$embed
);
+ $url = $match['file'] . $match['query'];
+ $file = $local . $match['file'];
+ if (
+ !CSSMin::isRemoteUrl( $url ) && !CSSMin::isLocalUrl( $url )
+ && file_exists( $file )
+ ) {
+ $mimeTypes[ CSSMin::getMimeType( $file ) ] = true;
+ }
+
return CSSMin::buildUrlValue( $embedded );
},
$rule
);
+
+ // Are all referenced images SVGs?
+ $needsEmbedFallback = $mimeTypes !== array( 'image/svg+xml' => true );
}
- 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
+ if ( !$embedData || $ruleWithEmbedded === $ruleWithRemapped ) {
+ // We're not embedding anything, or we tried to but the file is not embeddable
+ return $ruleWithRemapped;
+ } elseif ( $embedData && $needsEmbedFallback ) {
+ // Build 2 CSS properties; one which uses a data URI in place of the @embed comment, and
+ // the other with a remapped and versioned URL with an Internet Explorer 6 and 7 hack
// making it ignored in all browsers that support data URIs
return "$ruleWithEmbedded;$ruleWithRemapped!ie";
} else {
- // No reason to repeat twice
- return $ruleWithRemapped;
+ // Look ma, no fallbacks! This is for files which IE 6 and 7 don't support anyway: SVG.
+ return $ruleWithEmbedded;
}
}, $source );
@@ -289,6 +338,34 @@ class CSSMin {
}
/**
+ * Is this CSS rule referencing a remote URL?
+ *
+ * @private Until we require PHP 5.5 and we can access self:: from closures.
+ * @param string $maybeUrl
+ * @return bool
+ */
+ public static function isRemoteUrl( $maybeUrl ) {
+ if ( substr( $maybeUrl, 0, 2 ) === '//' || parse_url( $maybeUrl, PHP_URL_SCHEME ) ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Is this CSS rule referencing a local URL?
+ *
+ * @private Until we require PHP 5.5 and we can access self:: from closures.
+ * @param string $maybeUrl
+ * @return bool
+ */
+ public static function isLocalUrl( $maybeUrl ) {
+ if ( $maybeUrl !== '' && $maybeUrl[0] === '/' && !self::isRemoteUrl( $maybeUrl ) ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Remap or embed a CSS URL path.
*
* @param string $file URL to remap/embed
@@ -302,22 +379,16 @@ class CSSMin {
// 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;
+ // Expand local URLs with absolute paths like /w/index.php to possibly protocol-relative URL, if
+ // wfExpandUrl() is available. (This will not be the case if we're running outside of MW.)
+ if ( self::isLocalUrl( $url ) && function_exists( 'wfExpandUrl' ) ) {
+ return wfExpandUrl( $url, PROTO_RELATIVE );
}
- // 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;
- }
+ // Pass thru fully-qualified and protocol-relative URLs and data URIs, as well as local URLs if
+ // we can't expand them.
+ if ( self::isRemoteUrl( $url ) || self::isLocalUrl( $url ) ) {
+ return $url;
}
if ( $local === false ) {
diff --git a/includes/Cookie.php b/includes/libs/Cookie.php
index cb041904..0fe94444 100644
--- a/includes/Cookie.php
+++ b/includes/libs/Cookie.php
@@ -48,7 +48,7 @@ class Cookie {
* expires A date string
* path The path this cookie is used on
* domain Domain this cookie is used on
- * @throws MWException
+ * @throws InvalidArgumentException
*/
public function set( $value, $attr ) {
$this->value = $value;
@@ -69,7 +69,7 @@ class Cookie {
$this->domain = $attr['domain'];
}
} else {
- throw new MWException( 'You must specify a domain.' );
+ throw new InvalidArgumentException( '$attr must contain a domain' );
}
}
diff --git a/includes/libs/DeferredStringifier.php b/includes/libs/DeferredStringifier.php
new file mode 100644
index 00000000..a6fd11a4
--- /dev/null
+++ b/includes/libs/DeferredStringifier.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Class that defers a slow string generation until the string is actually needed.
+ *
+ * This 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.25
+ */
+class DeferredStringifier {
+ /** @var callable Callback used for result string generation */
+ private $callback;
+
+ /** @var array */
+ private $params;
+
+ /** @var string */
+ private $result;
+
+ /**
+ * @param callable $callback Callback that gets called by __toString
+ * @param mixed $param,... Parameters to the callback
+ */
+ public function __construct( $callback /*...*/ ) {
+ $this->params = func_get_args();
+ array_shift( $this->params );
+ $this->callback = $callback;
+ }
+
+ /**
+ * Get the string generated from the callback
+ *
+ * @return string
+ */
+ public function __toString() {
+ if ( $this->result === null ) {
+ $this->result = call_user_func_array( $this->callback, $this->params );
+ }
+ return $this->result;
+ }
+}
diff --git a/includes/libs/ExplodeIterator.php b/includes/libs/ExplodeIterator.php
new file mode 100644
index 00000000..3b34d9bc
--- /dev/null
+++ b/includes/libs/ExplodeIterator.php
@@ -0,0 +1,116 @@
+<?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 iterator which works exactly like:
+ *
+ * foreach ( explode( $delim, $s ) as $element ) {
+ * ...
+ * }
+ *
+ * Except it doesn't use 193 byte per element
+ */
+class ExplodeIterator implements Iterator {
+ // The subject string
+ private $subject, $subjectLength;
+
+ // The delimiter
+ private $delim, $delimLength;
+
+ // The position of the start of the line
+ private $curPos;
+
+ // The position after the end of the next delimiter
+ private $endPos;
+
+ // The current token
+ private $current;
+
+ /**
+ * Construct a DelimIterator
+ * @param string $delim
+ * @param string $subject
+ */
+ public function __construct( $delim, $subject ) {
+ $this->subject = $subject;
+ $this->delim = $delim;
+
+ // Micro-optimisation (theoretical)
+ $this->subjectLength = strlen( $subject );
+ $this->delimLength = strlen( $delim );
+
+ $this->rewind();
+ }
+
+ public function rewind() {
+ $this->curPos = 0;
+ $this->endPos = strpos( $this->subject, $this->delim );
+ $this->refreshCurrent();
+ }
+
+ public function refreshCurrent() {
+ if ( $this->curPos === false ) {
+ $this->current = false;
+ } elseif ( $this->curPos >= $this->subjectLength ) {
+ $this->current = '';
+ } elseif ( $this->endPos === false ) {
+ $this->current = substr( $this->subject, $this->curPos );
+ } else {
+ $this->current = substr( $this->subject, $this->curPos, $this->endPos - $this->curPos );
+ }
+ }
+
+ public function current() {
+ return $this->current;
+ }
+
+ /**
+ * @return int|bool Current position or boolean false if invalid
+ */
+ public function key() {
+ return $this->curPos;
+ }
+
+ /**
+ * @return string
+ */
+ public function next() {
+ if ( $this->endPos === false ) {
+ $this->curPos = false;
+ } else {
+ $this->curPos = $this->endPos + $this->delimLength;
+ if ( $this->curPos >= $this->subjectLength ) {
+ $this->endPos = false;
+ } else {
+ $this->endPos = strpos( $this->subject, $this->delim, $this->curPos );
+ }
+ }
+ $this->refreshCurrent();
+
+ return $this->current;
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid() {
+ return $this->curPos !== false;
+ }
+}
diff --git a/includes/libs/GenericArrayObject.php b/includes/libs/GenericArrayObject.php
index db8a7ecf..93ae83b2 100644
--- a/includes/libs/GenericArrayObject.php
+++ b/includes/libs/GenericArrayObject.php
@@ -117,7 +117,7 @@ abstract class GenericArrayObject extends ArrayObject {
*
* @param mixed $value
*
- * @return boolean
+ * @return bool
*/
protected function hasValidType( $value ) {
$class = $this->getObjectType();
@@ -171,7 +171,7 @@ abstract class GenericArrayObject extends ArrayObject {
* @param integer|string $index
* @param mixed $value
*
- * @return boolean
+ * @return bool
*/
protected function preSetElement( $index, $value ) {
return true;
@@ -232,7 +232,7 @@ abstract class GenericArrayObject extends ArrayObject {
*
* @since 1.20
*
- * @return boolean
+ * @return bool
*/
public function isEmpty() {
return $this->count() === 0;
diff --git a/includes/libs/IPSet.php b/includes/libs/IPSet.php
index ae593785..c1c841e6 100644
--- a/includes/libs/IPSet.php
+++ b/includes/libs/IPSet.php
@@ -1,6 +1,5 @@
<?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
@@ -163,7 +162,7 @@ class IPSet {
* Match an IP address against the set
*
* @param string $ip string IPv[46] address
- * @return boolean true is match success, false is match failure
+ * @return bool true is match success, false is match failure
*
* If $ip is unparseable, inet_pton may issue an E_WARNING to that effect
*/
diff --git a/includes/cache/MapCacheLRU.php b/includes/libs/MapCacheLRU.php
index 95e3af76..0b6db32e 100644
--- a/includes/cache/MapCacheLRU.php
+++ b/includes/libs/MapCacheLRU.php
@@ -38,11 +38,11 @@ class MapCacheLRU {
/**
* @param int $maxKeys Maximum number of entries allowed (min 1).
- * @throws MWException When $maxCacheKeys is not an int or =< 0.
+ * @throws Exception 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" );
+ throw new Exception( __METHOD__ . " must be given an integer and >= 1" );
}
$this->maxCacheKeys = $maxKeys;
}
@@ -95,6 +95,14 @@ class MapCacheLRU {
}
/**
+ * @return array
+ * @since 1.25
+ */
+ public function getAllKeys() {
+ return array_keys( $this->cache );
+ }
+
+ /**
* Clear one or several cache entries, or all cache entries
*
* @param string|array $keys
diff --git a/includes/libs/MessageSpecifier.php b/includes/libs/MessageSpecifier.php
new file mode 100644
index 00000000..b417f299
--- /dev/null
+++ b/includes/libs/MessageSpecifier.php
@@ -0,0 +1,39 @@
+<?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 MessageSpecifier {
+ /**
+ * 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 has been fetched, this method will return
+ * the key that was actually used to fetch the message.
+ *
+ * @return string
+ */
+ public function getKey();
+
+ /**
+ * Returns the message parameters
+ *
+ * @return array
+ */
+ public function getParams();
+}
diff --git a/includes/libs/MultiHttpClient.php b/includes/libs/MultiHttpClient.php
index 8c982c43..fb2daa69 100644
--- a/includes/libs/MultiHttpClient.php
+++ b/includes/libs/MultiHttpClient.php
@@ -34,6 +34,7 @@
* 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
+ * - proxy : HTTP proxy to use
* Request maps can use integer index 0 instead of 'method' and 1 instead of 'url'.
*
* @author Aaron Schulz
@@ -52,13 +53,17 @@ class MultiHttpClient {
protected $usePipelining = false;
/** @var integer */
protected $maxConnsPerHost = 50;
+ /** @var string|null proxy */
+ protected $proxy;
/**
* @param array $options
* - connTimeout : default connection timeout
* - reqTimeout : default request timeout
+ * - proxy : HTTP proxy to use
* - usePipelining : whether to use HTTP pipelining if possible (for all hosts)
* - maxConnsPerHost : maximum number of concurrent connections (per host)
+ * @throws Exception
*/
public function __construct( array $options ) {
if ( isset( $options['caBundlePath'] ) ) {
@@ -67,7 +72,7 @@ class MultiHttpClient {
throw new Exception( "Cannot find CA bundle: " . $this->caBundlePath );
}
}
- static $opts = array( 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost' );
+ static $opts = array( 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost', 'proxy' );
foreach ( $opts as $key ) {
if ( isset( $options[$key] ) ) {
$this->$key = $options[$key];
@@ -83,7 +88,7 @@ class MultiHttpClient {
* - 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
+ * - error : 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 );
@@ -103,14 +108,14 @@ class MultiHttpClient {
* 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 : 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)
+ * - error : 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
@@ -123,6 +128,7 @@ class MultiHttpClient {
* - 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
+ * @throws Exception
*/
public function runMulti( array $reqs, array $opts = array() ) {
$chm = $this->getCurlMulti();
@@ -244,12 +250,14 @@ class MultiHttpClient {
* - connTimeout : default connection timeout
* - reqTimeout : default request timeout
* @return resource
+ * @throws Exception
*/
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_PROXY, isset( $req['proxy'] ) ? $req['proxy'] : $this->proxy );
curl_setopt( $ch, CURLOPT_TIMEOUT,
isset( $opts['reqTimeout'] ) ? $opts['reqTimeout'] : $this->reqTimeout );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
diff --git a/includes/libs/ObjectFactory.php b/includes/libs/ObjectFactory.php
new file mode 100644
index 00000000..ec8c36a1
--- /dev/null
+++ b/includes/libs/ObjectFactory.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
+ */
+
+/**
+ * Construct objects from configuration instructions.
+ *
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ */
+class ObjectFactory {
+
+ /**
+ * Instantiate an object based on a specification array.
+ *
+ * The specification array must contain a 'class' key with string value
+ * that specifies the class name to instantiate or a 'factory' key with
+ * a callable (is_callable() === true). It can optionally contain
+ * an 'args' key that provides arguments to pass to the
+ * constructor/callable.
+ *
+ * Object construction using a specification having both 'class' and
+ * 'args' members will call the constructor of the class using
+ * ReflectionClass::newInstanceArgs. The use of ReflectionClass carries
+ * a performance penalty and should not be used to create large numbers of
+ * objects. If this is needed, consider introducing a factory method that
+ * can be called via call_user_func_array() instead.
+ *
+ * Values in the arguments collection which are Closure instances will be
+ * expanded by invoking them with no arguments before passing the
+ * resulting value on to the constructor/callable. This can be used to
+ * pass DatabaseBase instances or other live objects to the
+ * constructor/callable. This behavior can be suppressed by adding
+ * closure_expansion => false to the specification.
+ *
+ * @param array $spec Object specification
+ * @return object
+ * @throws InvalidArgumentException when object specification does not
+ * contain 'class' or 'factory' keys
+ * @throws ReflectionException when 'args' are supplied and 'class'
+ * constructor is non-public or non-existent
+ */
+ public static function getObjectFromSpec( $spec ) {
+ $args = isset( $spec['args'] ) ? $spec['args'] : array();
+
+ if ( !isset( $spec['closure_expansion'] ) ||
+ $spec['closure_expansion'] === true
+ ) {
+ $args = array_map( function ( $value ) {
+ if ( is_object( $value ) && $value instanceof Closure ) {
+ // If an argument is a Closure, call it.
+ return $value();
+ } else {
+ return $value;
+ }
+ }, $args );
+ }
+
+ if ( isset( $spec['class'] ) ) {
+ $clazz = $spec['class'];
+ if ( !$args ) {
+ $obj = new $clazz();
+ } else {
+ $ref = new ReflectionClass( $clazz );
+ $obj = $ref->newInstanceArgs( $args );
+ }
+ } elseif ( isset( $spec['factory'] ) ) {
+ $obj = call_user_func_array( $spec['factory'], $args );
+ } else {
+ throw new InvalidArgumentException(
+ 'Provided specification lacks both factory and class parameters.'
+ );
+ }
+
+ return $obj;
+ }
+}
diff --git a/includes/libs/ProcessCacheLRU.php b/includes/libs/ProcessCacheLRU.php
index f988207a..8d80eb38 100644
--- a/includes/libs/ProcessCacheLRU.php
+++ b/includes/libs/ProcessCacheLRU.php
@@ -28,13 +28,14 @@
class ProcessCacheLRU {
/** @var Array */
protected $cache = array(); // (key => prop => value)
+
/** @var Array */
protected $cacheTimes = array(); // (key => prop => UNIX timestamp)
protected $maxCacheKeys; // integer; max entries
/**
- * @param $maxKeys integer Maximum number of entries allowed (min 1).
+ * @param int $maxKeys Maximum number of entries allowed (min 1).
* @throws UnexpectedValueException When $maxCacheKeys is not an int or =< 0.
*/
public function __construct( $maxKeys ) {
@@ -46,9 +47,9 @@ class ProcessCacheLRU {
* 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 $key string
- * @param $prop string
- * @param $value mixed
+ * @param string $key
+ * @param string $prop
+ * @param mixed $value
* @return void
*/
public function set( $key, $prop, $value ) {
@@ -61,20 +62,22 @@ class ProcessCacheLRU {
unset( $this->cacheTimes[$evictKey] );
}
$this->cache[$key][$prop] = $value;
- $this->cacheTimes[$key][$prop] = time();
+ $this->cacheTimes[$key][$prop] = microtime( true );
}
/**
* Check if a property field exists for a cache entry.
*
- * @param $key string
- * @param $prop string
- * @param $maxAge integer Ignore items older than this many seconds (since 1.21)
+ * @param string $key
+ * @param string $prop
+ * @param float $maxAge Ignore items older than this many seconds (since 1.21)
* @return bool
*/
- public function has( $key, $prop, $maxAge = 0 ) {
+ public function has( $key, $prop, $maxAge = 0.0 ) {
if ( isset( $this->cache[$key][$prop] ) ) {
- return ( $maxAge <= 0 || ( time() - $this->cacheTimes[$key][$prop] ) <= $maxAge );
+ return ( $maxAge <= 0 ||
+ ( microtime( true ) - $this->cacheTimes[$key][$prop] ) <= $maxAge
+ );
}
return false;
@@ -85,13 +88,14 @@ class ProcessCacheLRU {
* This returns null if the property is not set.
* If the item is already set, it will be pushed to the top of the cache.
*
- * @param $key string
- * @param $prop string
+ * @param string $key
+ * @param string $prop
* @return mixed
*/
public function get( $key, $prop ) {
if ( isset( $this->cache[$key][$prop] ) ) {
- $this->ping( $key ); // push to top
+ // push to top
+ $this->ping( $key );
return $this->cache[$key][$prop];
} else {
return null;
@@ -99,9 +103,9 @@ class ProcessCacheLRU {
}
/**
- * Clear one or several cache entries, or all cache entries
+ * Clear one or several cache entries, or all cache entries.
*
- * @param $keys string|Array
+ * @param string|array $keys
* @return void
*/
public function clear( $keys = null ) {
@@ -119,8 +123,9 @@ class ProcessCacheLRU {
/**
* Resize the maximum number of cache entries, removing older entries as needed
*
- * @param $maxKeys integer
+ * @param int $maxKeys
* @return void
+ * @throws UnexpectedValueException
*/
public function resize( $maxKeys ) {
if ( !is_int( $maxKeys ) || $maxKeys < 1 ) {
@@ -138,7 +143,7 @@ class ProcessCacheLRU {
/**
* Push an entry to the top of the cache
*
- * @param $key string
+ * @param string $key
*/
protected function ping( $key ) {
$item = $this->cache[$key];
diff --git a/includes/libs/ReplacementArray.php b/includes/libs/ReplacementArray.php
new file mode 100644
index 00000000..7fdb3093
--- /dev/null
+++ b/includes/libs/ReplacementArray.php
@@ -0,0 +1,125 @@
+<?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
+ */
+
+/**
+ * Replacement array for FSS with fallback to strtr()
+ * Supports lazy initialisation of FSS resource
+ */
+class ReplacementArray {
+ private $data = false;
+ private $fss = false;
+
+ /**
+ * Create an object with the specified replacement array
+ * The array should have the same form as the replacement array for strtr()
+ * @param array $data
+ */
+ public function __construct( $data = array() ) {
+ $this->data = $data;
+ }
+
+ /**
+ * @return array
+ */
+ public function __sleep() {
+ return array( 'data' );
+ }
+
+ public function __wakeup() {
+ $this->fss = false;
+ }
+
+ /**
+ * Set the whole replacement array at once
+ * @param array $data
+ */
+ public function setArray( $data ) {
+ $this->data = $data;
+ $this->fss = false;
+ }
+
+ /**
+ * @return array|bool
+ */
+ public function getArray() {
+ return $this->data;
+ }
+
+ /**
+ * Set an element of the replacement array
+ * @param string $from
+ * @param string $to
+ */
+ public function setPair( $from, $to ) {
+ $this->data[$from] = $to;
+ $this->fss = false;
+ }
+
+ /**
+ * @param array $data
+ */
+ public function mergeArray( $data ) {
+ $this->data = array_merge( $this->data, $data );
+ $this->fss = false;
+ }
+
+ /**
+ * @param ReplacementArray $other
+ */
+ public function merge( ReplacementArray $other ) {
+ $this->data = array_merge( $this->data, $other->data );
+ $this->fss = false;
+ }
+
+ /**
+ * @param string $from
+ */
+ public function removePair( $from ) {
+ unset( $this->data[$from] );
+ $this->fss = false;
+ }
+
+ /**
+ * @param array $data
+ */
+ public function removeArray( $data ) {
+ foreach ( $data as $from => $to ) {
+ $this->removePair( $from );
+ }
+ $this->fss = false;
+ }
+
+ /**
+ * @param string $subject
+ * @return string
+ */
+ public function replace( $subject ) {
+ if ( function_exists( 'fss_prep_replace' ) ) {
+ if ( $this->fss === false ) {
+ $this->fss = fss_prep_replace( $this->data );
+ }
+ $result = fss_exec_replace( $this->fss, $subject );
+ } else {
+ $result = strtr( $subject, $this->data );
+ }
+
+ return $result;
+ }
+}
diff --git a/includes/libs/RunningStat.php b/includes/libs/RunningStat.php
index dda5254e..8bd4656c 100644
--- a/includes/libs/RunningStat.php
+++ b/includes/libs/RunningStat.php
@@ -60,10 +60,10 @@ class RunningStat implements Countable {
/** @var float The second central moment (or variance). **/
public $m2 = 0.0;
- /** @var float The least value in the the set. **/
+ /** @var float The least value in the set. **/
public $min = INF;
- /** @var float The most value in the set. **/
+ /** @var float The greatest value in the set. **/
public $max = NEGATIVE_INF;
/**
@@ -126,10 +126,10 @@ class RunningStat implements Countable {
}
/**
- * Get the estimated stanard deviation.
+ * Get the estimated standard 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
+ * its variance. It 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.
*
diff --git a/includes/libs/ScopedCallback.php b/includes/libs/ScopedCallback.php
index 631b6519..1ec9eaa6 100644
--- a/includes/libs/ScopedCallback.php
+++ b/includes/libs/ScopedCallback.php
@@ -28,16 +28,20 @@
class ScopedCallback {
/** @var callable */
protected $callback;
+ /** @var array */
+ protected $params;
/**
- * @param callable $callback
+ * @param callable|null $callback
+ * @param array $params Callback arguments (since 1.25)
* @throws Exception
*/
- public function __construct( $callback ) {
- if ( !is_callable( $callback ) ) {
+ public function __construct( $callback, array $params = array() ) {
+ if ( $callback !== null && !is_callable( $callback ) ) {
throw new InvalidArgumentException( "Provided callback is not valid." );
}
$this->callback = $callback;
+ $this->params = $params;
}
/**
@@ -67,7 +71,7 @@ class ScopedCallback {
*/
function __destruct() {
if ( $this->callback !== null ) {
- call_user_func( $this->callback );
+ call_user_func_array( $this->callback, $this->params );
}
}
}
diff --git a/includes/libs/StatusValue.php b/includes/libs/StatusValue.php
new file mode 100644
index 00000000..3c2dd409
--- /dev/null
+++ b/includes/libs/StatusValue.php
@@ -0,0 +1,316 @@
+<?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
+ */
+
+/**
+ * Generic operation result class
+ * Has warning/error list, boolean status and arbitrary value
+ *
+ * "Good" means the operation was completed with no warnings or errors.
+ *
+ * "OK" means the operation was partially or wholly completed.
+ *
+ * An operation which is not OK should have errors so that the user can be
+ * informed as to what went wrong. Calling the fatal() function sets an error
+ * message and simultaneously switches off the OK flag.
+ *
+ * The recommended pattern for Status objects is to return a StatusValue
+ * unconditionally, i.e. both on success and on failure -- so that the
+ * developer of the calling code is reminded that the function can fail, and
+ * so that a lack of error-handling will be explicit.
+ *
+ * The use of Message objects should be avoided when serializability is needed.
+ *
+ * @since 1.25
+ */
+class StatusValue {
+ /** @var bool */
+ protected $ok = true;
+ /** @var array */
+ protected $errors = array();
+
+ /** @var mixed */
+ public $value;
+ /** @var array Map of (key => bool) to indicate success of each part of batch operations */
+ public $success = array();
+ /** @var int Counter for batch operations */
+ public $successCount = 0;
+ /** @var int Counter for batch operations */
+ public $failCount = 0;
+
+ /**
+ * Factory function for fatal errors
+ *
+ * @param string|MessageSpecifier $message Message key or object
+ * @return Status
+ */
+ public static function newFatal( $message /*, parameters...*/ ) {
+ $params = func_get_args();
+ $result = new static();
+ call_user_func_array( array( &$result, 'fatal' ), $params );
+ return $result;
+ }
+
+ /**
+ * Factory function for good results
+ *
+ * @param mixed $value
+ * @return Status
+ */
+ public static function newGood( $value = null ) {
+ $result = new static();
+ $result->value = $value;
+ return $result;
+ }
+
+ /**
+ * Returns whether the operation completed and didn't have any error or
+ * warnings
+ *
+ * @return bool
+ */
+ public function isGood() {
+ return $this->ok && !$this->errors;
+ }
+
+ /**
+ * Returns whether the operation completed
+ *
+ * @return bool
+ */
+ public function isOK() {
+ return $this->ok;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Get the list of errors
+ *
+ * Each error is a (message:string or MessageSpecifier,params:array) map
+ *
+ * @return array
+ */
+ public function getErrors() {
+ return $this->errors;
+ }
+
+ /**
+ * Change operation status
+ *
+ * @param bool $ok
+ */
+ public function setOK( $ok ) {
+ $this->ok = $ok;
+ }
+
+ /**
+ * Change operation resuklt
+ *
+ * @param bool $ok Whether the operation completed
+ * @param mixed $value
+ */
+ public function setResult( $ok, $value = null ) {
+ $this->ok = $ok;
+ $this->value = $value;
+ }
+
+ /**
+ * Add a new warning
+ *
+ * @param string|MessageSpecifier $message Message key or object
+ */
+ public function warning( $message /*, parameters... */ ) {
+ $this->errors[] = array(
+ 'type' => 'warning',
+ 'message' => $message,
+ 'params' => array_slice( func_get_args(), 1 )
+ );
+ }
+
+ /**
+ * Add an error, do not set fatal flag
+ * This can be used for non-fatal errors
+ *
+ * @param string|MessageSpecifier $message Message key or object
+ */
+ public function error( $message /*, parameters... */ ) {
+ $this->errors[] = array(
+ 'type' => 'error',
+ 'message' => $message,
+ 'params' => array_slice( func_get_args(), 1 )
+ );
+ }
+
+ /**
+ * Add an error and set OK to false, indicating that the operation
+ * as a whole was fatal
+ *
+ * @param string|MessageSpecifier $message Message key or object
+ */
+ public function fatal( $message /*, parameters... */ ) {
+ $this->errors[] = array(
+ 'type' => 'error',
+ 'message' => $message,
+ 'params' => array_slice( func_get_args(), 1 )
+ );
+ $this->ok = false;
+ }
+
+ /**
+ * Merge another status object into this one
+ *
+ * @param Status $other Other Status object
+ * @param bool $overwriteValue Whether to override the "value" member
+ */
+ public function merge( $other, $overwriteValue = false ) {
+ $this->errors = array_merge( $this->errors, $other->errors );
+ $this->ok = $this->ok && $other->ok;
+ if ( $overwriteValue ) {
+ $this->value = $other->value;
+ }
+ $this->successCount += $other->successCount;
+ $this->failCount += $other->failCount;
+ }
+
+ /**
+ * Returns a list of status messages of the given type
+ *
+ * Each entry is a map of (message:string or MessageSpecifier,params:array))
+ *
+ * @param string $type
+ * @return array
+ */
+ public function getErrorsByType( $type ) {
+ $result = array();
+ foreach ( $this->errors as $error ) {
+ if ( $error['type'] === $type ) {
+ $result[] = $error;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns true if the specified message is present as a warning or error
+ *
+ * @param string|MessageSpecifier $message Message key or object to search for
+ *
+ * @return bool
+ */
+ public function hasMessage( $message ) {
+ if ( $message instanceof MessageSpecifier ) {
+ $message = $message->getKey();
+ }
+ foreach ( $this->errors as $error ) {
+ if ( $error['message'] instanceof MessageSpecifier
+ && $error['message']->getKey() === $message
+ ) {
+ return true;
+ } elseif ( $error['message'] === $message ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * If the specified source message exists, replace it with the specified
+ * destination message, but keep the same parameters as in the original error.
+ *
+ * Note, due to the lack of tools for comparing IStatusMessage objects, this
+ * function will not work when using such an object as the search parameter.
+ *
+ * @param IStatusMessage|string $source Message key or object to search for
+ * @param IStatusMessage|string $dest Replacement message key or object
+ * @return bool Return true if the replacement was done, false otherwise.
+ */
+ public function replaceMessage( $source, $dest ) {
+ $replaced = false;
+
+ foreach ( $this->errors as $index => $error ) {
+ if ( $error['message'] === $source ) {
+ $this->errors[$index]['message'] = $dest;
+ $replaced = true;
+ }
+ }
+
+ return $replaced;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString() {
+ $status = $this->isOK() ? "OK" : "Error";
+ if ( count( $this->errors ) ) {
+ $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
+ } else {
+ $errorcount = "no errors detected";
+ }
+ if ( isset( $this->value ) ) {
+ $valstr = gettype( $this->value ) . " value set";
+ if ( is_object( $this->value ) ) {
+ $valstr .= "\"" . get_class( $this->value ) . "\" instance";
+ }
+ } else {
+ $valstr = "no value set";
+ }
+ $out = sprintf( "<%s, %s, %s>",
+ $status,
+ $errorcount,
+ $valstr
+ );
+ if ( count( $this->errors ) > 0 ) {
+ $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
+ $i = 1;
+ $out .= "\n";
+ $out .= $hdr;
+ foreach ( $this->errors as $error ) {
+ if ( $error['message'] instanceof MessageSpecifier ) {
+ $key = $error['message']->getKey();
+ $params = $error['message']->getParams();
+ } elseif ( $error['params'] ) {
+ $key = $error['message'];
+ $params = $error['params'];
+ } else {
+ $key = $error['message'];
+ $params = array();
+ }
+
+ $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
+ $i,
+ $key,
+ implode( " ", $params )
+ );
+ $i += 1;
+ }
+ $out .= $hdr;
+ }
+
+ return $out;
+ }
+}
diff --git a/includes/utils/StringUtils.php b/includes/libs/StringUtils.php
index 86f45122..11ae0b26 100644
--- a/includes/utils/StringUtils.php
+++ b/includes/libs/StringUtils.php
@@ -162,7 +162,7 @@ class StringUtils {
* @param callable $callback Function to call on each match
* @param string $subject
* @param string $flags Regular expression flags
- * @throws MWException
+ * @throws InvalidArgumentException
* @return string
*/
static function delimiterReplaceCallback( $startDelim, $endDelim, $callback,
@@ -197,7 +197,7 @@ class StringUtils {
$tokenType = 'end';
$tokenLength = strlen( $m[0][0] );
} else {
- throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
+ throw new InvalidArgumentException( 'Invalid delimiter given to ' . __METHOD__ );
}
if ( $tokenType == 'start' ) {
@@ -230,7 +230,7 @@ class StringUtils {
}
$inputPos = $outputPos = $tokenOffset + $tokenLength;
} else {
- throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
+ throw new InvalidArgumentException( 'Invalid delimiter given to ' . __METHOD__ );
}
}
if ( $outputPos < strlen( $subject ) ) {
@@ -315,298 +315,3 @@ class StringUtils {
}
}
}
-
-/**
- * Base class for "replacers", objects used in preg_replace_callback() and
- * StringUtils::delimiterReplaceCallback()
- */
-class Replacer {
- /**
- * @return array
- */
- function cb() {
- return array( &$this, 'replace' );
- }
-}
-
-/**
- * Class to replace regex matches with a string similar to that used in preg_replace()
- */
-class RegexlikeReplacer extends Replacer {
- private $r;
-
- /**
- * @param string $r
- */
- function __construct( $r ) {
- $this->r = $r;
- }
-
- /**
- * @param array $matches
- * @return string
- */
- function replace( $matches ) {
- $pairs = array();
- 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 mixed $from
- * @param mixed $to
- * @param int $index
- */
- function __construct( $from, $to, $index = 0 ) {
- $this->from = $from;
- $this->to = $to;
- $this->index = $index;
- }
-
- /**
- * @param array $matches
- * @return mixed
- */
- function replace( $matches ) {
- return str_replace( $this->from, $this->to, $matches[$this->index] );
- }
-}
-
-/**
- * Class to perform replacement based on a simple hashtable lookup
- */
-class HashtableReplacer extends Replacer {
- private $table, $index;
-
- /**
- * @param array $table
- * @param int $index
- */
- function __construct( $table, $index = 0 ) {
- $this->table = $table;
- $this->index = $index;
- }
-
- /**
- * @param array $matches
- * @return mixed
- */
- function replace( $matches ) {
- return $this->table[$matches[$this->index]];
- }
-}
-
-/**
- * Replacement array for FSS with fallback to strtr()
- * Supports lazy initialisation of FSS resource
- */
-class ReplacementArray {
- private $data = false;
- private $fss = false;
-
- /**
- * Create an object with the specified replacement array
- * The array should have the same form as the replacement array for strtr()
- * @param array $data
- */
- function __construct( $data = array() ) {
- $this->data = $data;
- }
-
- /**
- * @return array
- */
- function __sleep() {
- return array( 'data' );
- }
-
- function __wakeup() {
- $this->fss = false;
- }
-
- /**
- * Set the whole replacement array at once
- * @param array $data
- */
- function setArray( $data ) {
- $this->data = $data;
- $this->fss = false;
- }
-
- /**
- * @return array|bool
- */
- function getArray() {
- return $this->data;
- }
-
- /**
- * Set an element of the replacement array
- * @param string $from
- * @param string $to
- */
- function setPair( $from, $to ) {
- $this->data[$from] = $to;
- $this->fss = false;
- }
-
- /**
- * @param array $data
- */
- function mergeArray( $data ) {
- $this->data = array_merge( $this->data, $data );
- $this->fss = false;
- }
-
- /**
- * @param ReplacementArray $other
- */
- function merge( $other ) {
- $this->data = array_merge( $this->data, $other->data );
- $this->fss = false;
- }
-
- /**
- * @param string $from
- */
- function removePair( $from ) {
- unset( $this->data[$from] );
- $this->fss = false;
- }
-
- /**
- * @param array $data
- */
- function removeArray( $data ) {
- foreach ( $data as $from => $to ) {
- $this->removePair( $from );
- }
- $this->fss = false;
- }
-
- /**
- * @param string $subject
- * @return string
- */
- function replace( $subject ) {
- if ( function_exists( 'fss_prep_replace' ) ) {
- wfProfileIn( __METHOD__ . '-fss' );
- if ( $this->fss === false ) {
- $this->fss = fss_prep_replace( $this->data );
- }
- $result = fss_exec_replace( $this->fss, $subject );
- wfProfileOut( __METHOD__ . '-fss' );
- } else {
- wfProfileIn( __METHOD__ . '-strtr' );
- $result = strtr( $subject, $this->data );
- wfProfileOut( __METHOD__ . '-strtr' );
- }
-
- return $result;
- }
-}
-
-/**
- * An iterator which works exactly like:
- *
- * foreach ( explode( $delim, $s ) as $element ) {
- * ...
- * }
- *
- * Except it doesn't use 193 byte per element
- */
-class ExplodeIterator implements Iterator {
- // The subject string
- private $subject, $subjectLength;
-
- // The delimiter
- private $delim, $delimLength;
-
- // The position of the start of the line
- private $curPos;
-
- // The position after the end of the next delimiter
- private $endPos;
-
- // The current token
- private $current;
-
- /**
- * Construct a DelimIterator
- * @param string $delim
- * @param string $subject
- */
- function __construct( $delim, $subject ) {
- $this->subject = $subject;
- $this->delim = $delim;
-
- // Micro-optimisation (theoretical)
- $this->subjectLength = strlen( $subject );
- $this->delimLength = strlen( $delim );
-
- $this->rewind();
- }
-
- function rewind() {
- $this->curPos = 0;
- $this->endPos = strpos( $this->subject, $this->delim );
- $this->refreshCurrent();
- }
-
- function refreshCurrent() {
- if ( $this->curPos === false ) {
- $this->current = false;
- } elseif ( $this->curPos >= $this->subjectLength ) {
- $this->current = '';
- } elseif ( $this->endPos === false ) {
- $this->current = substr( $this->subject, $this->curPos );
- } else {
- $this->current = substr( $this->subject, $this->curPos, $this->endPos - $this->curPos );
- }
- }
-
- function current() {
- return $this->current;
- }
-
- /**
- * @return int|bool Current position or boolean false if invalid
- */
- function key() {
- return $this->curPos;
- }
-
- /**
- * @return string
- */
- function next() {
- if ( $this->endPos === false ) {
- $this->curPos = false;
- } else {
- $this->curPos = $this->endPos + $this->delimLength;
- if ( $this->curPos >= $this->subjectLength ) {
- $this->endPos = false;
- } else {
- $this->endPos = strpos( $this->subject, $this->delim, $this->curPos );
- }
- }
- $this->refreshCurrent();
-
- return $this->current;
- }
-
- /**
- * @return bool
- */
- function valid() {
- return $this->curPos !== false;
- }
-}
diff --git a/includes/libs/UDPTransport.php b/includes/libs/UDPTransport.php
new file mode 100644
index 00000000..7fad882a
--- /dev/null
+++ b/includes/libs/UDPTransport.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
+ */
+
+/**
+ * A generic class to send a message over UDP
+ *
+ * If a message prefix is provided to the constructor or via
+ * UDPTransport::newFromString(), the payload of the UDP datagrams emitted
+ * will be formatted with the prefix and a single space at the start of each
+ * line. This is the payload format expected by the udp2log service.
+ *
+ * @since 1.25
+ */
+class UDPTransport {
+ private $host, $port, $prefix, $domain;
+
+ /**
+ * @param string $host IP address to send to
+ * @param int $port port number
+ * @param int $domain AF_INET or AF_INET6 constant
+ * @param string|bool $prefix Prefix to use, false for no prefix
+ */
+ public function __construct( $host, $port, $domain, $prefix = false ) {
+ $this->host = $host;
+ $this->port = $port;
+ $this->domain = $domain;
+ $this->prefix = $prefix;
+ }
+
+ /**
+ * @param string $info In the format of "udp://host:port/prefix"
+ * @return UDPTransport
+ * @throws InvalidArgumentException
+ */
+ public static function newFromString( $info ) {
+ if ( preg_match( '!^udp:(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $info, $m ) ) {
+ // IPv6 bracketed host
+ $host = $m[1];
+ $port = intval( $m[2] );
+ $prefix = isset( $m[3] ) ? $m[3] : false;
+ $domain = AF_INET6;
+ } elseif ( preg_match( '!^udp:(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $info, $m ) ) {
+ $host = $m[1];
+ if ( !IP::isIPv4( $host ) ) {
+ $host = gethostbyname( $host );
+ }
+ $port = intval( $m[2] );
+ $prefix = isset( $m[3] ) ? $m[3] : false;
+ $domain = AF_INET;
+ } else {
+ throw new InvalidArgumentException( __METHOD__ . ': Invalid UDP specification' );
+ }
+
+ return new self( $host, $port, $domain, $prefix );
+ }
+
+ /**
+ * @param string $text
+ */
+ public function emit( $text ) {
+ // Clean it up for the multiplexer
+ if ( $this->prefix !== false ) {
+ $text = preg_replace( '/^/m', $this->prefix . ' ', $text );
+
+ // Limit to 64KB
+ if ( strlen( $text ) > 65506 ) {
+ $text = substr( $text, 0, 65506 );
+ }
+
+ if ( substr( $text, -1 ) != "\n" ) {
+ $text .= "\n";
+ }
+ } elseif ( strlen( $text ) > 65507 ) {
+ $text = substr( $text, 0, 65507 );
+ }
+
+ $sock = socket_create( $this->domain, SOCK_DGRAM, SOL_UDP );
+ if ( !$sock ) { // @todo should this throw an exception?
+ return;
+ }
+
+ socket_sendto( $sock, $text, strlen( $text ), 0, $this->host, $this->port );
+ socket_close( $sock );
+ }
+}
diff --git a/includes/libs/Xhprof.php b/includes/libs/Xhprof.php
new file mode 100644
index 00000000..5ed67c73
--- /dev/null
+++ b/includes/libs/Xhprof.php
@@ -0,0 +1,445 @@
+<?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
+ */
+
+/**
+ * Convenience class for working with XHProf
+ * <https://github.com/phacility/xhprof>. XHProf can be installed as a PECL
+ * package for use with PHP5 (Zend PHP) and is built-in to HHVM 3.3.0.
+ *
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ * @since 1.25
+ */
+class Xhprof {
+
+ /**
+ * @var array $config
+ */
+ protected $config;
+
+ /**
+ * Hierarchical profiling data returned by xhprof.
+ * @var array $hieraData
+ */
+ protected $hieraData;
+
+ /**
+ * Per-function inclusive data.
+ * @var array $inclusive
+ */
+ protected $inclusive;
+
+ /**
+ * Per-function inclusive and exclusive data.
+ * @var array $complete
+ */
+ protected $complete;
+
+ /**
+ * Configuration data can contain:
+ * - flags: Optional flags to add additional information to the
+ * profiling data collected.
+ * (XHPROF_FLAGS_NO_BUILTINS, XHPROF_FLAGS_CPU,
+ * XHPROF_FLAGS_MEMORY)
+ * - exclude: Array of function names to exclude from profiling.
+ * - include: Array of function names to include in profiling.
+ * - sort: Key to sort per-function reports on.
+ *
+ * Note: When running under HHVM, xhprof will always behave as though the
+ * XHPROF_FLAGS_NO_BUILTINS flag has been used unless the
+ * Eval.JitEnableRenameFunction option is enabled for the HHVM process.
+ *
+ * @param array $config
+ */
+ public function __construct( array $config = array() ) {
+ $this->config = array_merge(
+ array(
+ 'flags' => 0,
+ 'exclude' => array(),
+ 'include' => null,
+ 'sort' => 'wt',
+ ),
+ $config
+ );
+
+ xhprof_enable( $this->config['flags'], array(
+ 'ignored_functions' => $this->config['exclude']
+ ) );
+ }
+
+ /**
+ * Stop collecting profiling data.
+ *
+ * Only the first invocation of this method will effect the internal
+ * object state. Subsequent calls will return the data collected by the
+ * initial call.
+ *
+ * @return array Collected profiling data (possibly cached)
+ */
+ public function stop() {
+ if ( $this->hieraData === null ) {
+ $this->hieraData = $this->pruneData( xhprof_disable() );
+ }
+ return $this->hieraData;
+ }
+
+ /**
+ * Load raw data from a prior run for analysis.
+ * Stops any existing data collection and clears internal caches.
+ *
+ * Any 'include' filters configured for this Xhprof instance will be
+ * enforced on the data as it is loaded. 'exclude' filters will however
+ * not be enforced as they are an XHProf intrinsic behavior.
+ *
+ * @param array $data
+ * @see getRawData()
+ */
+ public function loadRawData( array $data ) {
+ $this->stop();
+ $this->inclusive = null;
+ $this->complete = null;
+ $this->hieraData = $this->pruneData( $data );
+ }
+
+ /**
+ * Get raw data collected by xhprof.
+ *
+ * If data collection has not been stopped yet this method will halt
+ * collection to gather the profiling data.
+ *
+ * Each key in the returned array is an edge label for the call graph in
+ * the form "caller==>callee". There is once special case edge labled
+ * simply "main()" which represents the global scope entry point of the
+ * application.
+ *
+ * XHProf will collect different data depending on the flags that are used:
+ * - ct: Number of matching events seen.
+ * - wt: Inclusive elapsed wall time for this event in microseconds.
+ * - cpu: Inclusive elapsed cpu time for this event in microseconds.
+ * (XHPROF_FLAGS_CPU)
+ * - mu: Delta of memory usage from start to end of callee in bytes.
+ * (XHPROF_FLAGS_MEMORY)
+ * - pmu: Delta of peak memory usage from start to end of callee in
+ * bytes. (XHPROF_FLAGS_MEMORY)
+ * - alloc: Delta of amount memory requested from malloc() by the callee,
+ * in bytes. (XHPROF_FLAGS_MALLOC)
+ * - free: Delta of amount of memory passed to free() by the callee, in
+ * bytes. (XHPROF_FLAGS_MALLOC)
+ *
+ * @return array
+ * @see stop()
+ * @see getInclusiveMetrics()
+ * @see getCompleteMetrics()
+ */
+ public function getRawData() {
+ return $this->stop();
+ }
+
+ /**
+ * Convert an xhprof data key into an array of ['parent', 'child']
+ * function names.
+ *
+ * The resulting array is left padded with nulls, so a key
+ * with no parent (eg 'main()') will return [null, 'function'].
+ *
+ * @return array
+ */
+ public static function splitKey( $key ) {
+ return array_pad( explode( '==>', $key, 2 ), -2, null );
+ }
+
+ /**
+ * Remove data for functions that are not included in the 'include'
+ * configuration array.
+ *
+ * @param array $data Raw xhprof data
+ * @return array
+ */
+ protected function pruneData( $data ) {
+ if ( !$this->config['include'] ) {
+ return $data;
+ }
+
+ $want = array_fill_keys( $this->config['include'], true );
+ $want['main()'] = true;
+
+ $keep = array();
+ foreach ( $data as $key => $stats ) {
+ list( $parent, $child ) = self::splitKey( $key );
+ if ( isset( $want[$parent] ) || isset( $want[$child] ) ) {
+ $keep[$key] = $stats;
+ }
+ }
+ return $keep;
+ }
+
+ /**
+ * Get the inclusive metrics for each function call. Inclusive metrics
+ * for given function include the metrics for all functions that were
+ * called from that function during the measurement period.
+ *
+ * If data collection has not been stopped yet this method will halt
+ * collection to gather the profiling data.
+ *
+ * See getRawData() for a description of the metric that are returned for
+ * each funcition call. The values for the wt, cpu, mu and pmu metrics are
+ * arrays with these values:
+ * - total: Cumulative value
+ * - min: Minimum value
+ * - mean: Mean (average) value
+ * - max: Maximum value
+ * - variance: Variance (spread) of the values
+ *
+ * @return array
+ * @see getRawData()
+ * @see getCompleteMetrics()
+ */
+ public function getInclusiveMetrics() {
+ if ( $this->inclusive === null ) {
+ // Make sure we have data to work with
+ $this->stop();
+
+ $main = $this->hieraData['main()'];
+ $hasCpu = isset( $main['cpu'] );
+ $hasMu = isset( $main['mu'] );
+ $hasAlloc = isset( $main['alloc'] );
+
+ $this->inclusive = array();
+ foreach ( $this->hieraData as $key => $stats ) {
+ list( $parent, $child ) = self::splitKey( $key );
+ if ( !isset( $this->inclusive[$child] ) ) {
+ $this->inclusive[$child] = array(
+ 'ct' => 0,
+ 'wt' => new RunningStat(),
+ );
+ if ( $hasCpu ) {
+ $this->inclusive[$child]['cpu'] = new RunningStat();
+ }
+ if ( $hasMu ) {
+ $this->inclusive[$child]['mu'] = new RunningStat();
+ $this->inclusive[$child]['pmu'] = new RunningStat();
+ }
+ if ( $hasAlloc ) {
+ $this->inclusive[$child]['alloc'] = new RunningStat();
+ $this->inclusive[$child]['free'] = new RunningStat();
+ }
+ }
+
+ $this->inclusive[$child]['ct'] += $stats['ct'];
+ foreach ( $stats as $stat => $value ) {
+ if ( $stat === 'ct' ) {
+ continue;
+ }
+
+ if ( !isset( $this->inclusive[$child][$stat] ) ) {
+ // Ignore unknown stats
+ continue;
+ }
+
+ for ( $i = 0; $i < $stats['ct']; $i++ ) {
+ $this->inclusive[$child][$stat]->push(
+ $value / $stats['ct']
+ );
+ }
+ }
+ }
+
+ // Convert RunningStat instances to static arrays and add
+ // percentage stats.
+ foreach ( $this->inclusive as $func => $stats ) {
+ foreach ( $stats as $name => $value ) {
+ if ( $value instanceof RunningStat ) {
+ $total = $value->m1 * $value->n;
+ $percent = ( isset( $main[$name] ) && $main[$name] )
+ ? 100 * $total / $main[$name]
+ : 0;
+ $this->inclusive[$func][$name] = array(
+ 'total' => $total,
+ 'min' => $value->min,
+ 'mean' => $value->m1,
+ 'max' => $value->max,
+ 'variance' => $value->m2,
+ 'percent' => $percent,
+ );
+ }
+ }
+ }
+
+ uasort( $this->inclusive, self::makeSortFunction(
+ $this->config['sort'], 'total'
+ ) );
+ }
+ return $this->inclusive;
+ }
+
+ /**
+ * Get the inclusive and exclusive metrics for each function call.
+ *
+ * If data collection has not been stopped yet this method will halt
+ * collection to gather the profiling data.
+ *
+ * In addition to the normal data contained in the inclusive metrics, the
+ * metrics have an additional 'exclusive' measurement which is the total
+ * minus the totals of all child function calls.
+ *
+ * @return array
+ * @see getRawData()
+ * @see getInclusiveMetrics()
+ */
+ public function getCompleteMetrics() {
+ if ( $this->complete === null ) {
+ // Start with inclusive data
+ $this->complete = $this->getInclusiveMetrics();
+
+ foreach ( $this->complete as $func => $stats ) {
+ foreach ( $stats as $stat => $value ) {
+ if ( $stat === 'ct' ) {
+ continue;
+ }
+ // Initialize exclusive data with inclusive totals
+ $this->complete[$func][$stat]['exclusive'] = $value['total'];
+ }
+ // Add sapce for call tree information to be filled in later
+ $this->complete[$func]['calls'] = array();
+ $this->complete[$func]['subcalls'] = array();
+ }
+
+ foreach ( $this->hieraData as $key => $stats ) {
+ list( $parent, $child ) = self::splitKey( $key );
+ if ( $parent !== null ) {
+ // Track call tree information
+ $this->complete[$child]['calls'][$parent] = $stats;
+ $this->complete[$parent]['subcalls'][$child] = $stats;
+ }
+
+ if ( isset( $this->complete[$parent] ) ) {
+ // Deduct child inclusive data from exclusive data
+ foreach ( $stats as $stat => $value ) {
+ if ( $stat === 'ct' ) {
+ continue;
+ }
+
+ if ( !isset( $this->complete[$parent][$stat] ) ) {
+ // Ignore unknown stats
+ continue;
+ }
+
+ $this->complete[$parent][$stat]['exclusive'] -= $value;
+ }
+ }
+ }
+
+ uasort( $this->complete, self::makeSortFunction(
+ $this->config['sort'], 'exclusive'
+ ) );
+ }
+ return $this->complete;
+ }
+
+ /**
+ * Get a list of all callers of a given function.
+ *
+ * @param string $function Function name
+ * @return array
+ * @see getEdges()
+ */
+ public function getCallers( $function ) {
+ $edges = $this->getCompleteMetrics();
+ if ( isset( $edges[$function]['calls'] ) ) {
+ return array_keys( $edges[$function]['calls'] );
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Get a list of all callees from a given function.
+ *
+ * @param string $function Function name
+ * @return array
+ * @see getEdges()
+ */
+ public function getCallees( $function ) {
+ $edges = $this->getCompleteMetrics();
+ if ( isset( $edges[$function]['subcalls'] ) ) {
+ return array_keys( $edges[$function]['subcalls'] );
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Find the critical path for the given metric.
+ *
+ * @param string $metric Metric to find critical path for
+ * @return array
+ */
+ public function getCriticalPath( $metric = 'wt' ) {
+ $this->stop();
+ $func = 'main()';
+ $path = array(
+ $func => $this->hieraData[$func],
+ );
+ while ( $func ) {
+ $callees = $this->getCallees( $func );
+ $maxCallee = null;
+ $maxCall = null;
+ foreach ( $callees as $callee ) {
+ $call = "{$func}==>{$callee}";
+ if ( $maxCall === null ||
+ $this->hieraData[$call][$metric] >
+ $this->hieraData[$maxCall][$metric]
+ ) {
+ $maxCallee = $callee;
+ $maxCall = $call;
+ }
+ }
+ if ( $maxCall !== null ) {
+ $path[$maxCall] = $this->hieraData[$maxCall];
+ }
+ $func = $maxCallee;
+ }
+ return $path;
+ }
+
+ /**
+ * Make a closure to use as a sort function. The resulting function will
+ * sort by descending numeric values (largest value first).
+ *
+ * @param string $key Data key to sort on
+ * @param string $sub Sub key to sort array values on
+ * @return Closure
+ */
+ public static function makeSortFunction( $key, $sub ) {
+ return function ( $a, $b ) use ( $key, $sub ) {
+ if ( isset( $a[$key] ) && isset( $b[$key] ) ) {
+ // Descending sort: larger values will be first in result.
+ // Assumes all values are numeric.
+ // Values for 'main()' will not have sub keys
+ $valA = is_array( $a[$key] ) ? $a[$key][$sub] : $a[$key];
+ $valB = is_array( $b[$key] ) ? $b[$key][$sub] : $b[$key];
+ return $valB - $valA;
+ } else {
+ // Sort datum with the key before those without
+ return isset( $a[$key] ) ? -1 : 1;
+ }
+ };
+ }
+}
diff --git a/includes/libs/XmlTypeCheck.php b/includes/libs/XmlTypeCheck.php
index 31a4e28a..6d01986d 100644
--- a/includes/libs/XmlTypeCheck.php
+++ b/includes/libs/XmlTypeCheck.php
@@ -75,7 +75,7 @@ class XmlTypeCheck {
* SAX element handler event. This gives you access to the element
* namespace, name, attributes, and text contents.
* Filter should return 'true' to toggle on $this->filterMatch
- * @param boolean $isFile (optional) indicates if the first parameter is a
+ * @param bool $isFile (optional) indicates if the first parameter is a
* filename (default, true) or if it is a string (false)
* @param array $options list of additional parsing options:
* processing_instruction_handler: Callback for xml_set_processing_instruction_handler
diff --git a/includes/libs/composer/ComposerJson.php b/includes/libs/composer/ComposerJson.php
new file mode 100644
index 00000000..796acb56
--- /dev/null
+++ b/includes/libs/composer/ComposerJson.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Reads a composer.json file and provides accessors to get
+ * its hash and the required dependencies
+ *
+ * @since 1.25
+ */
+class ComposerJson {
+
+ /**
+ * @param string $location
+ */
+ public function __construct( $location ) {
+ $this->hash = md5_file( $location );
+ $this->contents = json_decode( file_get_contents( $location ), true );
+ }
+
+ public function getHash() {
+ return $this->hash;
+ }
+
+ /**
+ * Dependencies as specified by composer.json
+ *
+ * @return array
+ */
+ public function getRequiredDependencies() {
+ $deps = array();
+ foreach ( $this->contents['require'] as $package => $version ) {
+ if ( $package !== "php" && strpos( $package, 'ext-' ) !== 0 ) {
+ $deps[$package] = self::normalizeVersion( $version );
+ }
+ }
+
+ return $deps;
+ }
+
+ /**
+ * Strip a leading "v" from the version name
+ *
+ * @param string $version
+ * @return string
+ */
+ public static function normalizeVersion( $version ) {
+ if ( strpos( $version, 'v' ) === 0 ) {
+ // Composer auto-strips the "v" in front of the tag name
+ $version = ltrim( $version, 'v' );
+ }
+
+ return $version;
+ }
+
+}
diff --git a/includes/libs/composer/ComposerLock.php b/includes/libs/composer/ComposerLock.php
new file mode 100644
index 00000000..9c7bf2f9
--- /dev/null
+++ b/includes/libs/composer/ComposerLock.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Reads a composer.lock file and provides accessors to get
+ * its hash and what is installed
+ *
+ * @since 1.25
+ */
+class ComposerLock {
+
+ /**
+ * @param string $location
+ */
+ public function __construct( $location ) {
+ $this->contents = json_decode( file_get_contents( $location ), true );
+ }
+
+ public function getHash() {
+ return $this->contents['hash'];
+ }
+
+ /**
+ * Dependencies currently installed according to composer.lock
+ *
+ * @return array
+ */
+ public function getInstalledDependencies() {
+ $deps = array();
+ foreach ( $this->contents['packages'] as $installed ) {
+ $deps[$installed['name']] = array(
+ 'version' => ComposerJson::normalizeVersion( $installed['version'] ),
+ 'type' => $installed['type'],
+ );
+ }
+
+ return $deps;
+ }
+}
diff --git a/includes/libs/jsminplus.php b/includes/libs/jsminplus.php
index ed0382cf..99cf399b 100644
--- a/includes/libs/jsminplus.php
+++ b/includes/libs/jsminplus.php
@@ -1017,7 +1017,7 @@ class JSParser
case KEYWORD_CATCH:
case KEYWORD_FINALLY:
- throw $this->t->newSyntaxError($tt + ' without preceding try');
+ throw $this->t->newSyntaxError($tt . ' without preceding try');
case KEYWORD_THROW:
$n = new JSNode($this->t);
diff --git a/includes/libs/lessc.inc.php b/includes/libs/lessc.inc.php
deleted file mode 100644
index 61ed771a..00000000
--- a/includes/libs/lessc.inc.php
+++ /dev/null
@@ -1,3796 +0,0 @@
-<?php
-// @codingStandardsIgnoreFile File external to MediaWiki. Ignore coding conventions checks.
-/**
- * lessphp v0.4.0@2cc77e3c7b
- * http://leafo.net/lessphp
- *
- * LESS CSS compiler, adapted from http://lesscss.org
- *
- * For ease of distribution, lessphp 0.4.0 is under a dual license.
- * You are free to pick which one suits your needs.
- *
- * MIT LICENSE
- *
- * Copyright 2013, Leaf Corcoran <leafot@gmail.com>
- *
- * 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.
- *
- * GPL VERSION 3
- *
- * Please refer to http://www.gnu.org/licenses/gpl-3.0.html for the full
- * text of the GPL version 3
- */
-
-
-/**
- * The LESS compiler and parser.
- *
- * Converting LESS to CSS is a three stage process. The incoming file is parsed
- * by `lessc_parser` into a syntax tree, then it is compiled into another tree
- * representing the CSS structure by `lessc`. The CSS tree is fed into a
- * formatter, like `lessc_formatter` which then outputs CSS as a string.
- *
- * During the first compile, all values are *reduced*, which means that their
- * types are brought to the lowest form before being dump as strings. This
- * handles math equations, variable dereferences, and the like.
- *
- * The `parse` function of `lessc` is the entry point.
- *
- * In summary:
- *
- * The `lessc` class creates an instance of the parser, feeds it LESS code,
- * then transforms the resulting tree to a CSS tree. This class also holds the
- * evaluation context, such as all available mixins and variables at any given
- * time.
- *
- * The `lessc_parser` class is only concerned with parsing its input.
- *
- * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
- * handling things like indentation.
- */
-class lessc {
- static public $VERSION = "v0.4.0";
-
- static public $TRUE = array("keyword", "true");
- static public $FALSE = array("keyword", "false");
-
- protected $libFunctions = array();
- protected $registeredVars = array();
- protected $preserveComments = false;
-
- public $vPrefix = '@'; // prefix of abstract properties
- public $mPrefix = '$'; // prefix of abstract blocks
- public $parentSelector = '&';
-
- public $importDisabled = false;
- public $importDir = '';
-
- protected $numberPrecision = null;
-
- protected $allParsedFiles = array();
-
- // set to the parser that generated the current line when compiling
- // so we know how to create error messages
- protected $sourceParser = null;
- protected $sourceLoc = null;
-
- static protected $nextImportId = 0; // uniquely identify imports
-
- // attempts to find the path of an import url, returns null for css files
- protected function findImport($url) {
- foreach ((array)$this->importDir as $dir) {
- $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
- if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
- return $file;
- }
- }
-
- return null;
- }
-
- protected function fileExists($name) {
- return is_file($name);
- }
-
- static public function compressList($items, $delim) {
- if (!isset($items[1]) && isset($items[0])) return $items[0];
- else return array('list', $delim, $items);
- }
-
- static public function preg_quote($what) {
- return preg_quote($what, '/');
- }
-
- protected function tryImport($importPath, $parentBlock, $out) {
- if ($importPath[0] == "function" && $importPath[1] == "url") {
- $importPath = $this->flattenList($importPath[2]);
- }
-
- $str = $this->coerceString($importPath);
- if ($str === null) return false;
-
- $url = $this->compileValue($this->lib_e($str));
-
- // don't import if it ends in css
- if (substr_compare($url, '.css', -4, 4) === 0) return false;
-
- $realPath = $this->findImport($url);
-
- if ($realPath === null) return false;
-
- if ($this->importDisabled) {
- return array(false, "/* import disabled */");
- }
-
- if (isset($this->allParsedFiles[realpath($realPath)])) {
- return array(false, null);
- }
-
- $this->addParsedFile($realPath);
- $parser = $this->makeParser($realPath);
- $root = $parser->parse(file_get_contents($realPath));
-
- // set the parents of all the block props
- foreach ($root->props as $prop) {
- if ($prop[0] == "block") {
- $prop[1]->parent = $parentBlock;
- }
- }
-
- // copy mixins into scope, set their parents
- // bring blocks from import into current block
- // TODO: need to mark the source parser these came from this file
- foreach ($root->children as $childName => $child) {
- if (isset($parentBlock->children[$childName])) {
- $parentBlock->children[$childName] = array_merge(
- $parentBlock->children[$childName],
- $child);
- } else {
- $parentBlock->children[$childName] = $child;
- }
- }
-
- $pi = pathinfo($realPath);
- $dir = $pi["dirname"];
-
- list($top, $bottom) = $this->sortProps($root->props, true);
- $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
-
- return array(true, $bottom, $parser, $dir);
- }
-
- protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
- $oldSourceParser = $this->sourceParser;
-
- $oldImport = $this->importDir;
-
- // TODO: this is because the importDir api is stupid
- $this->importDir = (array)$this->importDir;
- array_unshift($this->importDir, $importDir);
-
- foreach ($props as $prop) {
- $this->compileProp($prop, $block, $out);
- }
-
- $this->importDir = $oldImport;
- $this->sourceParser = $oldSourceParser;
- }
-
- /**
- * Recursively compiles a block.
- *
- * A block is analogous to a CSS block in most cases. A single LESS document
- * is encapsulated in a block when parsed, but it does not have parent tags
- * so all of it's children appear on the root level when compiled.
- *
- * Blocks are made up of props and children.
- *
- * Props are property instructions, array tuples which describe an action
- * to be taken, eg. write a property, set a variable, mixin a block.
- *
- * The children of a block are just all the blocks that are defined within.
- * This is used to look up mixins when performing a mixin.
- *
- * Compiling the block involves pushing a fresh environment on the stack,
- * and iterating through the props, compiling each one.
- *
- * See lessc::compileProp()
- *
- */
- protected function compileBlock($block) {
- switch ($block->type) {
- case "root":
- $this->compileRoot($block);
- break;
- case null:
- $this->compileCSSBlock($block);
- break;
- case "media":
- $this->compileMedia($block);
- break;
- case "directive":
- $name = "@" . $block->name;
- if (!empty($block->value)) {
- $name .= " " . $this->compileValue($this->reduce($block->value));
- }
-
- $this->compileNestedBlock($block, array($name));
- break;
- default:
- $this->throwError("unknown block type: $block->type\n");
- }
- }
-
- protected function compileCSSBlock($block) {
- $env = $this->pushEnv();
-
- $selectors = $this->compileSelectors($block->tags);
- $env->selectors = $this->multiplySelectors($selectors);
- $out = $this->makeOutputBlock(null, $env->selectors);
-
- $this->scope->children[] = $out;
- $this->compileProps($block, $out);
-
- $block->scope = $env; // mixins carry scope with them!
- $this->popEnv();
- }
-
- protected function compileMedia($media) {
- $env = $this->pushEnv($media);
- $parentScope = $this->mediaParent($this->scope);
-
- $query = $this->compileMediaQuery($this->multiplyMedia($env));
-
- $this->scope = $this->makeOutputBlock($media->type, array($query));
- $parentScope->children[] = $this->scope;
-
- $this->compileProps($media, $this->scope);
-
- if (count($this->scope->lines) > 0) {
- $orphanSelelectors = $this->findClosestSelectors();
- if (!is_null($orphanSelelectors)) {
- $orphan = $this->makeOutputBlock(null, $orphanSelelectors);
- $orphan->lines = $this->scope->lines;
- array_unshift($this->scope->children, $orphan);
- $this->scope->lines = array();
- }
- }
-
- $this->scope = $this->scope->parent;
- $this->popEnv();
- }
-
- protected function mediaParent($scope) {
- while (!empty($scope->parent)) {
- if (!empty($scope->type) && $scope->type != "media") {
- break;
- }
- $scope = $scope->parent;
- }
-
- return $scope;
- }
-
- protected function compileNestedBlock($block, $selectors) {
- $this->pushEnv($block);
- $this->scope = $this->makeOutputBlock($block->type, $selectors);
- $this->scope->parent->children[] = $this->scope;
-
- $this->compileProps($block, $this->scope);
-
- $this->scope = $this->scope->parent;
- $this->popEnv();
- }
-
- protected function compileRoot($root) {
- $this->pushEnv();
- $this->scope = $this->makeOutputBlock($root->type);
- $this->compileProps($root, $this->scope);
- $this->popEnv();
- }
-
- protected function compileProps($block, $out) {
- foreach ($this->sortProps($block->props) as $prop) {
- $this->compileProp($prop, $block, $out);
- }
- $out->lines = $this->deduplicate($out->lines);
- }
-
- /**
- * Deduplicate lines in a block. Comments are not deduplicated. If a
- * duplicate rule is detected, the comments immediately preceding each
- * occurence are consolidated.
- */
- protected function deduplicate($lines) {
- $unique = array();
- $comments = array();
-
- foreach($lines as $line) {
- if (strpos($line, '/*') === 0) {
- $comments[] = $line;
- continue;
- }
- if (!in_array($line, $unique)) {
- $unique[] = $line;
- }
- array_splice($unique, array_search($line, $unique), 0, $comments);
- $comments = array();
- }
- return array_merge($unique, $comments);
- }
-
- protected function sortProps($props, $split = false) {
- $vars = array();
- $imports = array();
- $other = array();
- $stack = array();
-
- foreach ($props as $prop) {
- switch ($prop[0]) {
- case "comment":
- $stack[] = $prop;
- break;
- case "assign":
- $stack[] = $prop;
- if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
- $vars = array_merge($vars, $stack);
- } else {
- $other = array_merge($other, $stack);
- }
- $stack = array();
- break;
- case "import":
- $id = self::$nextImportId++;
- $prop[] = $id;
- $stack[] = $prop;
- $imports = array_merge($imports, $stack);
- $other[] = array("import_mixin", $id);
- $stack = array();
- break;
- default:
- $stack[] = $prop;
- $other = array_merge($other, $stack);
- $stack = array();
- break;
- }
- }
- $other = array_merge($other, $stack);
-
- if ($split) {
- return array(array_merge($vars, $imports), $other);
- } else {
- return array_merge($vars, $imports, $other);
- }
- }
-
- protected function compileMediaQuery($queries) {
- $compiledQueries = array();
- foreach ($queries as $query) {
- $parts = array();
- foreach ($query as $q) {
- switch ($q[0]) {
- case "mediaType":
- $parts[] = implode(" ", array_slice($q, 1));
- break;
- case "mediaExp":
- if (isset($q[2])) {
- $parts[] = "($q[1]: " .
- $this->compileValue($this->reduce($q[2])) . ")";
- } else {
- $parts[] = "($q[1])";
- }
- break;
- case "variable":
- $parts[] = $this->compileValue($this->reduce($q));
- break;
- }
- }
-
- if (count($parts) > 0) {
- $compiledQueries[] = implode(" and ", $parts);
- }
- }
-
- $out = "@media";
- if (!empty($parts)) {
- $out .= " " .
- implode($this->formatter->selectorSeparator, $compiledQueries);
- }
- return $out;
- }
-
- protected function multiplyMedia($env, $childQueries = null) {
- if (is_null($env) ||
- !empty($env->block->type) && $env->block->type != "media")
- {
- return $childQueries;
- }
-
- // plain old block, skip
- if (empty($env->block->type)) {
- return $this->multiplyMedia($env->parent, $childQueries);
- }
-
- $out = array();
- $queries = $env->block->queries;
- if (is_null($childQueries)) {
- $out = $queries;
- } else {
- foreach ($queries as $parent) {
- foreach ($childQueries as $child) {
- $out[] = array_merge($parent, $child);
- }
- }
- }
-
- return $this->multiplyMedia($env->parent, $out);
- }
-
- protected function expandParentSelectors(&$tag, $replace) {
- $parts = explode("$&$", $tag);
- $count = 0;
- foreach ($parts as &$part) {
- $part = str_replace($this->parentSelector, $replace, $part, $c);
- $count += $c;
- }
- $tag = implode($this->parentSelector, $parts);
- return $count;
- }
-
- protected function findClosestSelectors() {
- $env = $this->env;
- $selectors = null;
- while ($env !== null) {
- if (isset($env->selectors)) {
- $selectors = $env->selectors;
- break;
- }
- $env = $env->parent;
- }
-
- return $selectors;
- }
-
-
- // multiply $selectors against the nearest selectors in env
- protected function multiplySelectors($selectors) {
- // find parent selectors
-
- $parentSelectors = $this->findClosestSelectors();
- if (is_null($parentSelectors)) {
- // kill parent reference in top level selector
- foreach ($selectors as &$s) {
- $this->expandParentSelectors($s, "");
- }
-
- return $selectors;
- }
-
- $out = array();
- foreach ($parentSelectors as $parent) {
- foreach ($selectors as $child) {
- $count = $this->expandParentSelectors($child, $parent);
-
- // don't prepend the parent tag if & was used
- if ($count > 0) {
- $out[] = trim($child);
- } else {
- $out[] = trim($parent . ' ' . $child);
- }
- }
- }
-
- return $out;
- }
-
- // reduces selector expressions
- protected function compileSelectors($selectors) {
- $out = array();
-
- foreach ($selectors as $s) {
- if (is_array($s)) {
- list(, $value) = $s;
- $out[] = trim($this->compileValue($this->reduce($value)));
- } else {
- $out[] = $s;
- }
- }
-
- return $out;
- }
-
- protected function eq($left, $right) {
- return $left == $right;
- }
-
- protected function patternMatch($block, $orderedArgs, $keywordArgs) {
- // match the guards if it has them
- // any one of the groups must have all its guards pass for a match
- if (!empty($block->guards)) {
- $groupPassed = false;
- foreach ($block->guards as $guardGroup) {
- foreach ($guardGroup as $guard) {
- $this->pushEnv();
- $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
-
- $negate = false;
- if ($guard[0] == "negate") {
- $guard = $guard[1];
- $negate = true;
- }
-
- $passed = $this->reduce($guard) == self::$TRUE;
- if ($negate) $passed = !$passed;
-
- $this->popEnv();
-
- if ($passed) {
- $groupPassed = true;
- } else {
- $groupPassed = false;
- break;
- }
- }
-
- if ($groupPassed) break;
- }
-
- if (!$groupPassed) {
- return false;
- }
- }
-
- if (empty($block->args)) {
- return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
- }
-
- $remainingArgs = $block->args;
- if ($keywordArgs) {
- $remainingArgs = array();
- foreach ($block->args as $arg) {
- if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
- continue;
- }
-
- $remainingArgs[] = $arg;
- }
- }
-
- $i = -1; // no args
- // try to match by arity or by argument literal
- foreach ($remainingArgs as $i => $arg) {
- switch ($arg[0]) {
- case "lit":
- if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
- return false;
- }
- break;
- case "arg":
- // no arg and no default value
- if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
- return false;
- }
- break;
- case "rest":
- $i--; // rest can be empty
- break 2;
- }
- }
-
- if ($block->isVararg) {
- return true; // not having enough is handled above
- } else {
- $numMatched = $i + 1;
- // greater than becuase default values always match
- return $numMatched >= count($orderedArgs);
- }
- }
-
- protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
- $matches = null;
- foreach ($blocks as $block) {
- // skip seen blocks that don't have arguments
- if (isset($skip[$block->id]) && !isset($block->args)) {
- continue;
- }
-
- if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
- $matches[] = $block;
- }
- }
-
- return $matches;
- }
-
- // attempt to find blocks matched by path and args
- protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
- if ($searchIn == null) return null;
- if (isset($seen[$searchIn->id])) return null;
- $seen[$searchIn->id] = true;
-
- $name = $path[0];
-
- if (isset($searchIn->children[$name])) {
- $blocks = $searchIn->children[$name];
- if (count($path) == 1) {
- $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
- if (!empty($matches)) {
- // This will return all blocks that match in the closest
- // scope that has any matching block, like lessjs
- return $matches;
- }
- } else {
- $matches = array();
- foreach ($blocks as $subBlock) {
- $subMatches = $this->findBlocks($subBlock,
- array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
-
- if (!is_null($subMatches)) {
- foreach ($subMatches as $sm) {
- $matches[] = $sm;
- }
- }
- }
-
- return count($matches) > 0 ? $matches : null;
- }
- }
- if ($searchIn->parent === $searchIn) return null;
- return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
- }
-
- // sets all argument names in $args to either the default value
- // or the one passed in through $values
- protected function zipSetArgs($args, $orderedValues, $keywordValues) {
- $assignedValues = array();
-
- $i = 0;
- foreach ($args as $a) {
- if ($a[0] == "arg") {
- if (isset($keywordValues[$a[1]])) {
- // has keyword arg
- $value = $keywordValues[$a[1]];
- } elseif (isset($orderedValues[$i])) {
- // has ordered arg
- $value = $orderedValues[$i];
- $i++;
- } elseif (isset($a[2])) {
- // has default value
- $value = $a[2];
- } else {
- $this->throwError("Failed to assign arg " . $a[1]);
- $value = null; // :(
- }
-
- $value = $this->reduce($value);
- $this->set($a[1], $value);
- $assignedValues[] = $value;
- } else {
- // a lit
- $i++;
- }
- }
-
- // check for a rest
- $last = end($args);
- if ($last[0] == "rest") {
- $rest = array_slice($orderedValues, count($args) - 1);
- $this->set($last[1], $this->reduce(array("list", " ", $rest)));
- }
-
- // wow is this the only true use of PHP's + operator for arrays?
- $this->env->arguments = $assignedValues + $orderedValues;
- }
-
- // compile a prop and update $lines or $blocks appropriately
- protected function compileProp($prop, $block, $out) {
- // set error position context
- $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
-
- switch ($prop[0]) {
- case 'assign':
- list(, $name, $value) = $prop;
- if ($name[0] == $this->vPrefix) {
- $this->set($name, $value);
- } else {
- $out->lines[] = $this->formatter->property($name,
- $this->compileValue($this->reduce($value)));
- }
- break;
- case 'block':
- list(, $child) = $prop;
- $this->compileBlock($child);
- break;
- case 'mixin':
- list(, $path, $args, $suffix) = $prop;
-
- $orderedArgs = array();
- $keywordArgs = array();
- foreach ((array)$args as $arg) {
- $argval = null;
- switch ($arg[0]) {
- case "arg":
- if (!isset($arg[2])) {
- $orderedArgs[] = $this->reduce(array("variable", $arg[1]));
- } else {
- $keywordArgs[$arg[1]] = $this->reduce($arg[2]);
- }
- break;
-
- case "lit":
- $orderedArgs[] = $this->reduce($arg[1]);
- break;
- default:
- $this->throwError("Unknown arg type: " . $arg[0]);
- }
- }
-
- $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
-
- if ($mixins === null) {
- $this->throwError("{$prop[1][0]} is undefined");
- }
-
- foreach ($mixins as $mixin) {
- if ($mixin === $block && !$orderedArgs) {
- continue;
- }
-
- $haveScope = false;
- if (isset($mixin->parent->scope)) {
- $haveScope = true;
- $mixinParentEnv = $this->pushEnv();
- $mixinParentEnv->storeParent = $mixin->parent->scope;
- }
-
- $haveArgs = false;
- if (isset($mixin->args)) {
- $haveArgs = true;
- $this->pushEnv();
- $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
- }
-
- $oldParent = $mixin->parent;
- if ($mixin != $block) $mixin->parent = $block;
-
- foreach ($this->sortProps($mixin->props) as $subProp) {
- if ($suffix !== null &&
- $subProp[0] == "assign" &&
- is_string($subProp[1]) &&
- $subProp[1]{0} != $this->vPrefix)
- {
- $subProp[2] = array(
- 'list', ' ',
- array($subProp[2], array('keyword', $suffix))
- );
- }
-
- $this->compileProp($subProp, $mixin, $out);
- }
-
- $mixin->parent = $oldParent;
-
- if ($haveArgs) $this->popEnv();
- if ($haveScope) $this->popEnv();
- }
-
- break;
- case 'raw':
- $out->lines[] = $prop[1];
- break;
- case "directive":
- list(, $name, $value) = $prop;
- $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
- break;
- case "comment":
- $out->lines[] = $prop[1];
- break;
- case "import";
- list(, $importPath, $importId) = $prop;
- $importPath = $this->reduce($importPath);
-
- if (!isset($this->env->imports)) {
- $this->env->imports = array();
- }
-
- $result = $this->tryImport($importPath, $block, $out);
-
- $this->env->imports[$importId] = $result === false ?
- array(false, "@import " . $this->compileValue($importPath).";") :
- $result;
-
- break;
- case "import_mixin":
- list(,$importId) = $prop;
- $import = $this->env->imports[$importId];
- if ($import[0] === false) {
- if (isset($import[1])) {
- $out->lines[] = $import[1];
- }
- } else {
- list(, $bottom, $parser, $importDir) = $import;
- $this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
- }
-
- break;
- default:
- $this->throwError("unknown op: {$prop[0]}\n");
- }
- }
-
-
- /**
- * Compiles a primitive value into a CSS property value.
- *
- * Values in lessphp are typed by being wrapped in arrays, their format is
- * typically:
- *
- * array(type, contents [, additional_contents]*)
- *
- * The input is expected to be reduced. This function will not work on
- * things like expressions and variables.
- */
- public function compileValue($value) {
- switch ($value[0]) {
- case 'list':
- // [1] - delimiter
- // [2] - array of values
- return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
- case 'raw_color':
- if (!empty($this->formatter->compressColors)) {
- return $this->compileValue($this->coerceColor($value));
- }
- return $value[1];
- case 'keyword':
- // [1] - the keyword
- return $value[1];
- case 'number':
- list(, $num, $unit) = $value;
- // [1] - the number
- // [2] - the unit
- if ($this->numberPrecision !== null) {
- $num = round($num, $this->numberPrecision);
- }
- return $num . $unit;
- case 'string':
- // [1] - contents of string (includes quotes)
- list(, $delim, $content) = $value;
- foreach ($content as &$part) {
- if (is_array($part)) {
- $part = $this->compileValue($part);
- }
- }
- return $delim . implode($content) . $delim;
- case 'color':
- // [1] - red component (either number or a %)
- // [2] - green component
- // [3] - blue component
- // [4] - optional alpha component
- list(, $r, $g, $b) = $value;
- $r = round($r);
- $g = round($g);
- $b = round($b);
-
- if (count($value) == 5 && $value[4] != 1) { // rgba
- return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
- }
-
- $h = sprintf("#%02x%02x%02x", $r, $g, $b);
-
- if (!empty($this->formatter->compressColors)) {
- // Converting hex color to short notation (e.g. #003399 to #039)
- if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
- $h = '#' . $h[1] . $h[3] . $h[5];
- }
- }
-
- return $h;
-
- case 'function':
- list(, $name, $args) = $value;
- return $name.'('.$this->compileValue($args).')';
- default: // assumed to be unit
- $this->throwError("unknown value type: $value[0]");
- }
- }
-
- protected function lib_pow($args) {
- list($base, $exp) = $this->assertArgs($args, 2, "pow");
- return pow($this->assertNumber($base), $this->assertNumber($exp));
- }
-
- protected function lib_pi() {
- return pi();
- }
-
- protected function lib_mod($args) {
- list($a, $b) = $this->assertArgs($args, 2, "mod");
- return $this->assertNumber($a) % $this->assertNumber($b);
- }
-
- protected function lib_tan($num) {
- return tan($this->assertNumber($num));
- }
-
- protected function lib_sin($num) {
- return sin($this->assertNumber($num));
- }
-
- protected function lib_cos($num) {
- return cos($this->assertNumber($num));
- }
-
- protected function lib_atan($num) {
- $num = atan($this->assertNumber($num));
- return array("number", $num, "rad");
- }
-
- protected function lib_asin($num) {
- $num = asin($this->assertNumber($num));
- return array("number", $num, "rad");
- }
-
- protected function lib_acos($num) {
- $num = acos($this->assertNumber($num));
- return array("number", $num, "rad");
- }
-
- protected function lib_sqrt($num) {
- return sqrt($this->assertNumber($num));
- }
-
- protected function lib_extract($value) {
- list($list, $idx) = $this->assertArgs($value, 2, "extract");
- $idx = $this->assertNumber($idx);
- // 1 indexed
- if ($list[0] == "list" && isset($list[2][$idx - 1])) {
- return $list[2][$idx - 1];
- }
- }
-
- protected function lib_isnumber($value) {
- return $this->toBool($value[0] == "number");
- }
-
- protected function lib_isstring($value) {
- return $this->toBool($value[0] == "string");
- }
-
- protected function lib_iscolor($value) {
- return $this->toBool($this->coerceColor($value));
- }
-
- protected function lib_iskeyword($value) {
- return $this->toBool($value[0] == "keyword");
- }
-
- protected function lib_ispixel($value) {
- return $this->toBool($value[0] == "number" && $value[2] == "px");
- }
-
- protected function lib_ispercentage($value) {
- return $this->toBool($value[0] == "number" && $value[2] == "%");
- }
-
- protected function lib_isem($value) {
- return $this->toBool($value[0] == "number" && $value[2] == "em");
- }
-
- protected function lib_isrem($value) {
- return $this->toBool($value[0] == "number" && $value[2] == "rem");
- }
-
- protected function lib_rgbahex($color) {
- $color = $this->coerceColor($color);
- if (is_null($color))
- $this->throwError("color expected for rgbahex");
-
- return sprintf("#%02x%02x%02x%02x",
- isset($color[4]) ? $color[4]*255 : 255,
- $color[1],$color[2], $color[3]);
- }
-
- protected function lib_argb($color){
- 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]) {
- case "list":
- $items = $arg[2];
- if (isset($items[0])) {
- return $this->lib_e($items[0]);
- }
- $this->throwError("unrecognised input");
- case "string":
- $arg[1] = "";
- return $arg;
- case "keyword":
- return $arg;
- default:
- return array("keyword", $this->compileValue($arg));
- }
- }
-
- protected function lib__sprintf($args) {
- if ($args[0] != "list") return $args;
- $values = $args[2];
- $string = array_shift($values);
- $template = $this->compileValue($this->lib_e($string));
-
- $i = 0;
- if (preg_match_all('/%[dsa]/', $template, $m)) {
- foreach ($m[0] as $match) {
- $val = isset($values[$i]) ?
- $this->reduce($values[$i]) : array('keyword', '');
-
- // lessjs compat, renders fully expanded color, not raw color
- if ($color = $this->coerceColor($val)) {
- $val = $color;
- }
-
- $i++;
- $rep = $this->compileValue($this->lib_e($val));
- $template = preg_replace('/'.self::preg_quote($match).'/',
- $rep, $template, 1);
- }
- }
-
- $d = $string[0] == "string" ? $string[1] : '"';
- return array("string", $d, array($template));
- }
-
- protected function lib_floor($arg) {
- $value = $this->assertNumber($arg);
- return array("number", floor($value), $arg[2]);
- }
-
- protected function lib_ceil($arg) {
- $value = $this->assertNumber($arg);
- return array("number", ceil($value), $arg[2]);
- }
-
- protected function lib_round($arg) {
- if($arg[0] != "list") {
- $value = $this->assertNumber($arg);
- return array("number", round($value), $arg[2]);
- } else {
- $value = $this->assertNumber($arg[2][0]);
- $precision = $this->assertNumber($arg[2][1]);
- return array("number", round($value, $precision), $arg[2][0][2]);
- }
- }
-
- protected function lib_unit($arg) {
- if ($arg[0] == "list") {
- list($number, $newUnit) = $arg[2];
- return array("number", $this->assertNumber($number),
- $this->compileValue($this->lib_e($newUnit)));
- } else {
- return array("number", $this->assertNumber($arg), "");
- }
- }
-
- /**
- * Helper function to get arguments for color manipulation functions.
- * takes a list that contains a color like thing and a percentage
- */
- public function colorArgs($args) {
- if ($args[0] != 'list' || count($args[2]) < 2) {
- return array(array('color', 0, 0, 0), 0);
- }
- list($color, $delta) = $args[2];
- $color = $this->assertColor($color);
- $delta = floatval($delta[1]);
-
- return array($color, $delta);
- }
-
- protected function lib_darken($args) {
- list($color, $delta) = $this->colorArgs($args);
-
- $hsl = $this->toHSL($color);
- $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
- return $this->toRGB($hsl);
- }
-
- protected function lib_lighten($args) {
- list($color, $delta) = $this->colorArgs($args);
-
- $hsl = $this->toHSL($color);
- $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
- return $this->toRGB($hsl);
- }
-
- protected function lib_saturate($args) {
- list($color, $delta) = $this->colorArgs($args);
-
- $hsl = $this->toHSL($color);
- $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
- return $this->toRGB($hsl);
- }
-
- protected function lib_desaturate($args) {
- list($color, $delta) = $this->colorArgs($args);
-
- $hsl = $this->toHSL($color);
- $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
- return $this->toRGB($hsl);
- }
-
- protected function lib_spin($args) {
- list($color, $delta) = $this->colorArgs($args);
-
- $hsl = $this->toHSL($color);
-
- $hsl[1] = $hsl[1] + $delta % 360;
- if ($hsl[1] < 0) $hsl[1] += 360;
-
- return $this->toRGB($hsl);
- }
-
- protected function lib_fadeout($args) {
- list($color, $delta) = $this->colorArgs($args);
- $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
- return $color;
- }
-
- protected function lib_fadein($args) {
- list($color, $delta) = $this->colorArgs($args);
- $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
- return $color;
- }
-
- protected function lib_hue($color) {
- $hsl = $this->toHSL($this->assertColor($color));
- return round($hsl[1]);
- }
-
- protected function lib_saturation($color) {
- $hsl = $this->toHSL($this->assertColor($color));
- return round($hsl[2]);
- }
-
- protected function lib_lightness($color) {
- $hsl = $this->toHSL($this->assertColor($color));
- return round($hsl[3]);
- }
-
- // get the alpha of a color
- // defaults to 1 for non-colors or colors without an alpha
- protected function lib_alpha($value) {
- if (!is_null($color = $this->coerceColor($value))) {
- return isset($color[4]) ? $color[4] : 1;
- }
- }
-
- // set the alpha of the color
- protected function lib_fade($args) {
- list($color, $alpha) = $this->colorArgs($args);
- $color[4] = $this->clamp($alpha / 100.0);
- return $color;
- }
-
- protected function lib_percentage($arg) {
- $num = $this->assertNumber($arg);
- return array("number", $num*100, "%");
- }
-
- // mixes two colors by weight
- // mix(@color1, @color2, [@weight: 50%]);
- // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
- protected function lib_mix($args) {
- if ($args[0] != "list" || count($args[2]) < 2)
- $this->throwError("mix expects (color1, color2, weight)");
-
- list($first, $second) = $args[2];
- $first = $this->assertColor($first);
- $second = $this->assertColor($second);
-
- $first_a = $this->lib_alpha($first);
- $second_a = $this->lib_alpha($second);
-
- if (isset($args[2][2])) {
- $weight = $args[2][2][1] / 100.0;
- } else {
- $weight = 0.5;
- }
-
- $w = $weight * 2 - 1;
- $a = $first_a - $second_a;
-
- $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
- $w2 = 1.0 - $w1;
-
- $new = array('color',
- $w1 * $first[1] + $w2 * $second[1],
- $w1 * $first[2] + $w2 * $second[2],
- $w1 * $first[3] + $w2 * $second[3],
- );
-
- if ($first_a != 1.0 || $second_a != 1.0) {
- $new[] = $first_a * $weight + $second_a * ($weight - 1);
- }
-
- return $this->fixColor($new);
- }
-
- protected function lib_contrast($args) {
- $darkColor = array('color', 0, 0, 0);
- $lightColor = array('color', 255, 255, 255);
- $threshold = 0.43;
-
- 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->coerceColor($inputColor);
- $darkColor = $this->coerceColor($darkColor);
- $lightColor = $this->coerceColor($lightColor);
-
- //Figure out which is actually light and dark!
- if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
- $t = $lightColor;
- $lightColor = $darkColor;
- $darkColor = $t;
- }
-
- $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);
- return $color;
- }
-
- public function assertNumber($value, $error = "expecting number") {
- if ($value[0] == "number") return $value[1];
- $this->throwError($error);
- }
-
- public function assertArgs($value, $expectedArgs, $name="") {
- if ($expectedArgs == 1) {
- return $value;
- } else {
- if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
- $values = $value[2];
- $numValues = count($values);
- if ($expectedArgs != $numValues) {
- if ($name) {
- $name = $name . ": ";
- }
-
- $this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
- }
-
- return $values;
- }
- }
-
- protected function toHSL($color) {
- if ($color[0] == 'hsl') return $color;
-
- $r = $color[1] / 255;
- $g = $color[2] / 255;
- $b = $color[3] / 255;
-
- $min = min($r, $g, $b);
- $max = max($r, $g, $b);
-
- $L = ($min + $max) / 2;
- if ($min == $max) {
- $S = $H = 0;
- } else {
- if ($L < 0.5)
- $S = ($max - $min)/($max + $min);
- else
- $S = ($max - $min)/(2.0 - $max - $min);
-
- if ($r == $max) $H = ($g - $b)/($max - $min);
- elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
- elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
-
- }
-
- $out = array('hsl',
- ($H < 0 ? $H + 6 : $H)*60,
- $S*100,
- $L*100,
- );
-
- if (count($color) > 4) $out[] = $color[4]; // copy alpha
- return $out;
- }
-
- protected function toRGB_helper($comp, $temp1, $temp2) {
- if ($comp < 0) $comp += 1.0;
- elseif ($comp > 1) $comp -= 1.0;
-
- if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
- if (2 * $comp < 1) return $temp2;
- if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
-
- return $temp1;
- }
-
- /**
- * Converts a hsl array into a color value in rgb.
- * Expects H to be in range of 0 to 360, S and L in 0 to 100
- */
- protected function toRGB($color) {
- if ($color[0] == 'color') return $color;
-
- $H = $color[1] / 360;
- $S = $color[2] / 100;
- $L = $color[3] / 100;
-
- if ($S == 0) {
- $r = $g = $b = $L;
- } else {
- $temp2 = $L < 0.5 ?
- $L*(1.0 + $S) :
- $L + $S - $L * $S;
-
- $temp1 = 2.0 * $L - $temp2;
-
- $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
- $g = $this->toRGB_helper($H, $temp1, $temp2);
- $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
- }
-
- // $out = array('color', round($r*255), round($g*255), round($b*255));
- $out = array('color', $r*255, $g*255, $b*255);
- if (count($color) > 4) $out[] = $color[4]; // copy alpha
- return $out;
- }
-
- protected function clamp($v, $max = 1, $min = 0) {
- return min($max, max($min, $v));
- }
-
- /**
- * Convert the rgb, rgba, hsl color literals of function type
- * as returned by the parser into values of color type.
- */
- protected function funcToColor($func) {
- $fname = $func[1];
- if ($func[2][0] != 'list') return false; // need a list of arguments
- $rawComponents = $func[2][2];
-
- if ($fname == 'hsl' || $fname == 'hsla') {
- $hsl = array('hsl');
- $i = 0;
- foreach ($rawComponents as $c) {
- $val = $this->reduce($c);
- $val = isset($val[1]) ? floatval($val[1]) : 0;
-
- if ($i == 0) $clamp = 360;
- elseif ($i < 3) $clamp = 100;
- else $clamp = 1;
-
- $hsl[] = $this->clamp($val, $clamp);
- $i++;
- }
-
- while (count($hsl) < 4) $hsl[] = 0;
- return $this->toRGB($hsl);
-
- } elseif ($fname == 'rgb' || $fname == 'rgba') {
- $components = array();
- $i = 1;
- foreach ($rawComponents as $c) {
- $c = $this->reduce($c);
- if ($i < 4) {
- if ($c[0] == "number" && $c[2] == "%") {
- $components[] = 255 * ($c[1] / 100);
- } else {
- $components[] = floatval($c[1]);
- }
- } elseif ($i == 4) {
- if ($c[0] == "number" && $c[2] == "%") {
- $components[] = 1.0 * ($c[1] / 100);
- } else {
- $components[] = floatval($c[1]);
- }
- } else break;
-
- $i++;
- }
- while (count($components) < 3) $components[] = 0;
- array_unshift($components, 'color');
- return $this->fixColor($components);
- }
-
- return false;
- }
-
- protected function reduce($value, $forExpression = false) {
- switch ($value[0]) {
- case "interpolate":
- $reduced = $this->reduce($value[1]);
- $var = $this->compileValue($reduced);
- $res = $this->reduce(array("variable", $this->vPrefix . $var));
-
- if ($res[0] == "raw_color") {
- $res = $this->coerceColor($res);
- }
-
- if (empty($value[2])) $res = $this->lib_e($res);
-
- return $res;
- case "variable":
- $key = $value[1];
- if (is_array($key)) {
- $key = $this->reduce($key);
- $key = $this->vPrefix . $this->compileValue($this->lib_e($key));
- }
-
- $seen =& $this->env->seenNames;
-
- if (!empty($seen[$key])) {
- $this->throwError("infinite loop detected: $key");
- }
-
- $seen[$key] = true;
- $out = $this->reduce($this->get($key));
- $seen[$key] = false;
- return $out;
- case "list":
- foreach ($value[2] as &$item) {
- $item = $this->reduce($item, $forExpression);
- }
- return $value;
- case "expression":
- return $this->evaluate($value);
- case "string":
- foreach ($value[2] as &$part) {
- if (is_array($part)) {
- $strip = $part[0] == "variable";
- $part = $this->reduce($part);
- if ($strip) $part = $this->lib_e($part);
- }
- }
- return $value;
- case "escape":
- list(,$inner) = $value;
- return $this->lib_e($this->reduce($inner));
- case "function":
- $color = $this->funcToColor($value);
- if ($color) return $color;
-
- list(, $name, $args) = $value;
- if ($name == "%") $name = "_sprintf";
-
- $f = isset($this->libFunctions[$name]) ?
- $this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
-
- if (is_callable($f)) {
- if ($args[0] == 'list')
- $args = self::compressList($args[2], $args[1]);
-
- $ret = call_user_func($f, $this->reduce($args, true), $this);
-
- if (is_null($ret)) {
- return array("string", "", array(
- $name, "(", $args, ")"
- ));
- }
-
- // convert to a typed value if the result is a php primitive
- if (is_numeric($ret)) $ret = array('number', $ret, "");
- elseif (!is_array($ret)) $ret = array('keyword', $ret);
-
- return $ret;
- }
-
- // plain function, reduce args
- $value[2] = $this->reduce($value[2]);
- return $value;
- case "unary":
- list(, $op, $exp) = $value;
- $exp = $this->reduce($exp);
-
- if ($exp[0] == "number") {
- switch ($op) {
- case "+":
- return $exp;
- case "-":
- $exp[1] *= -1;
- return $exp;
- }
- }
- return array("string", "", array($op, $exp));
- }
-
- if ($forExpression) {
- switch ($value[0]) {
- case "keyword":
- if ($color = $this->coerceColor($value)) {
- return $color;
- }
- break;
- case "raw_color":
- return $this->coerceColor($value);
- }
- }
-
- return $value;
- }
-
-
- // coerce a value for use in color operation
- protected function coerceColor($value) {
- switch($value[0]) {
- case 'color': return $value;
- case 'raw_color':
- $c = array("color", 0, 0, 0);
- $colorStr = substr($value[1], 1);
- $num = hexdec($colorStr);
- $width = strlen($colorStr) == 3 ? 16 : 256;
-
- for ($i = 3; $i > 0; $i--) { // 3 2 1
- $t = $num % $width;
- $num /= $width;
-
- $c[$i] = $t * (256/$width) + $t * floor(16/$width);
- }
-
- return $c;
- case 'keyword':
- $name = $value[1];
- if (isset(self::$cssColors[$name])) {
- $rgba = explode(',', self::$cssColors[$name]);
-
- if(isset($rgba[3]))
- return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
-
- return array('color', $rgba[0], $rgba[1], $rgba[2]);
- }
- return null;
- }
- }
-
- // make something string like into a string
- protected function coerceString($value) {
- switch ($value[0]) {
- case "string":
- return $value;
- case "keyword":
- return array("string", "", array($value[1]));
- }
- return null;
- }
-
- // turn list of length 1 into value type
- protected function flattenList($value) {
- if ($value[0] == "list" && count($value[2]) == 1) {
- return $this->flattenList($value[2][0]);
- }
- return $value;
- }
-
- public function toBool($a) {
- if ($a) return self::$TRUE;
- else return self::$FALSE;
- }
-
- // evaluate an expression
- protected function evaluate($exp) {
- list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
-
- $left = $this->reduce($left, true);
- $right = $this->reduce($right, true);
-
- if ($leftColor = $this->coerceColor($left)) {
- $left = $leftColor;
- }
-
- if ($rightColor = $this->coerceColor($right)) {
- $right = $rightColor;
- }
-
- $ltype = $left[0];
- $rtype = $right[0];
-
- // operators that work on all types
- if ($op == "and") {
- return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
- }
-
- if ($op == "=") {
- return $this->toBool($this->eq($left, $right) );
- }
-
- if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
- return $str;
- }
-
- // type based operators
- $fname = "op_${ltype}_${rtype}";
- if (is_callable(array($this, $fname))) {
- $out = $this->$fname($op, $left, $right);
- if (!is_null($out)) return $out;
- }
-
- // make the expression look it did before being parsed
- $paddedOp = $op;
- if ($whiteBefore) $paddedOp = " " . $paddedOp;
- if ($whiteAfter) $paddedOp .= " ";
-
- return array("string", "", array($left, $paddedOp, $right));
- }
-
- protected function stringConcatenate($left, $right) {
- if ($strLeft = $this->coerceString($left)) {
- if ($right[0] == "string") {
- $right[1] = "";
- }
- $strLeft[2][] = $right;
- return $strLeft;
- }
-
- if ($strRight = $this->coerceString($right)) {
- array_unshift($strRight[2], $left);
- return $strRight;
- }
- }
-
-
- // make sure a color's components don't go out of bounds
- protected function fixColor($c) {
- foreach (range(1, 3) as $i) {
- if ($c[$i] < 0) $c[$i] = 0;
- if ($c[$i] > 255) $c[$i] = 255;
- }
-
- return $c;
- }
-
- protected function op_number_color($op, $lft, $rgt) {
- if ($op == '+' || $op == '*') {
- return $this->op_color_number($op, $rgt, $lft);
- }
- }
-
- protected function op_color_number($op, $lft, $rgt) {
- if ($rgt[0] == '%') $rgt[1] /= 100;
-
- return $this->op_color_color($op, $lft,
- array_fill(1, count($lft) - 1, $rgt[1]));
- }
-
- protected function op_color_color($op, $left, $right) {
- $out = array('color');
- $max = count($left) > count($right) ? count($left) : count($right);
- foreach (range(1, $max - 1) as $i) {
- $lval = isset($left[$i]) ? $left[$i] : 0;
- $rval = isset($right[$i]) ? $right[$i] : 0;
- switch ($op) {
- case '+':
- $out[] = $lval + $rval;
- break;
- case '-':
- $out[] = $lval - $rval;
- break;
- case '*':
- $out[] = $lval * $rval;
- break;
- case '%':
- $out[] = $lval % $rval;
- break;
- case '/':
- if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
- $out[] = $lval / $rval;
- break;
- default:
- $this->throwError('evaluate error: color op number failed on op '.$op);
- }
- }
- return $this->fixColor($out);
- }
-
- function lib_red($color){
- $color = $this->coerceColor($color);
- if (is_null($color)) {
- $this->throwError('color expected for red()');
- }
-
- return $color[1];
- }
-
- function lib_green($color){
- $color = $this->coerceColor($color);
- if (is_null($color)) {
- $this->throwError('color expected for green()');
- }
-
- return $color[2];
- }
-
- function lib_blue($color){
- $color = $this->coerceColor($color);
- if (is_null($color)) {
- $this->throwError('color expected for blue()');
- }
-
- return $color[3];
- }
-
-
- // operator on two numbers
- protected function op_number_number($op, $left, $right) {
- $unit = empty($left[2]) ? $right[2] : $left[2];
-
- $value = 0;
- switch ($op) {
- case '+':
- $value = $left[1] + $right[1];
- break;
- case '*':
- $value = $left[1] * $right[1];
- break;
- case '-':
- $value = $left[1] - $right[1];
- break;
- case '%':
- $value = $left[1] % $right[1];
- break;
- case '/':
- if ($right[1] == 0) $this->throwError('parse error: divide by zero');
- $value = $left[1] / $right[1];
- break;
- case '<':
- return $this->toBool($left[1] < $right[1]);
- case '>':
- return $this->toBool($left[1] > $right[1]);
- case '>=':
- return $this->toBool($left[1] >= $right[1]);
- case '=<':
- return $this->toBool($left[1] <= $right[1]);
- default:
- $this->throwError('parse error: unknown number operator: '.$op);
- }
-
- return array("number", $value, $unit);
- }
-
-
- /* environment functions */
-
- protected function makeOutputBlock($type, $selectors = null) {
- $b = new stdclass;
- $b->lines = array();
- $b->children = array();
- $b->selectors = $selectors;
- $b->type = $type;
- $b->parent = $this->scope;
- return $b;
- }
-
- // the state of execution
- protected function pushEnv($block = null) {
- $e = new stdclass;
- $e->parent = $this->env;
- $e->store = array();
- $e->block = $block;
-
- $this->env = $e;
- return $e;
- }
-
- // pop something off the stack
- protected function popEnv() {
- $old = $this->env;
- $this->env = $this->env->parent;
- return $old;
- }
-
- // set something in the current env
- protected function set($name, $value) {
- $this->env->store[$name] = $value;
- }
-
-
- // get the highest occurrence entry for a name
- protected function get($name) {
- $current = $this->env;
-
- $isArguments = $name == $this->vPrefix . 'arguments';
- while ($current) {
- if ($isArguments && isset($current->arguments)) {
- return array('list', ' ', $current->arguments);
- }
-
- if (isset($current->store[$name]))
- return $current->store[$name];
- else {
- $current = isset($current->storeParent) ?
- $current->storeParent : $current->parent;
- }
- }
-
- $this->throwError("variable $name is undefined");
- }
-
- // inject array of unparsed strings into environment as variables
- protected function injectVariables($args) {
- $this->pushEnv();
- $parser = new lessc_parser($this, __METHOD__);
- foreach ($args as $name => $strValue) {
- if ($name{0} != '@') $name = '@'.$name;
- $parser->count = 0;
- $parser->buffer = (string)$strValue;
- if (!$parser->propertyValue($value)) {
- throw new Exception("failed to parse passed in variable $name: $strValue");
- }
-
- $this->set($name, $value);
- }
- }
-
- /**
- * Initialize any static state, can initialize parser for a file
- * $opts isn't used yet
- */
- public function __construct($fname = null) {
- if ($fname !== null) {
- // used for deprecated parse method
- $this->_parseFile = $fname;
- }
- }
-
- public function compile($string, $name = null) {
- $locale = setlocale(LC_NUMERIC, 0);
- setlocale(LC_NUMERIC, "C");
-
- $this->parser = $this->makeParser($name);
- $root = $this->parser->parse($string);
-
- $this->env = null;
- $this->scope = null;
-
- $this->formatter = $this->newFormatter();
-
- if (!empty($this->registeredVars)) {
- $this->injectVariables($this->registeredVars);
- }
-
- $this->sourceParser = $this->parser; // used for error messages
- $this->compileBlock($root);
-
- ob_start();
- $this->formatter->block($this->scope);
- $out = ob_get_clean();
- setlocale(LC_NUMERIC, $locale);
- return $out;
- }
-
- public function compileFile($fname, $outFname = null) {
- if (!is_readable($fname)) {
- throw new Exception('load error: failed to find '.$fname);
- }
-
- $pi = pathinfo($fname);
-
- $oldImport = $this->importDir;
-
- $this->importDir = (array)$this->importDir;
- $this->importDir[] = $pi['dirname'].'/';
-
- $this->addParsedFile($fname);
-
- $out = $this->compile(file_get_contents($fname), $fname);
-
- $this->importDir = $oldImport;
-
- if ($outFname !== null) {
- return file_put_contents($outFname, $out);
- }
-
- return $out;
- }
-
- // compile only if changed input has changed or output doesn't exist
- public function checkedCompile($in, $out) {
- if (!is_file($out) || filemtime($in) > filemtime($out)) {
- $this->compileFile($in, $out);
- return true;
- }
- return false;
- }
-
- /**
- * Execute lessphp on a .less file or a lessphp cache structure
- *
- * The lessphp cache structure contains information about a specific
- * less file having been parsed. It can be used as a hint for future
- * calls to determine whether or not a rebuild is required.
- *
- * The cache structure contains two important keys that may be used
- * externally:
- *
- * compiled: The final compiled CSS
- * updated: The time (in seconds) the CSS was last compiled
- *
- * The cache structure is a plain-ol' PHP associative array and can
- * be serialized and unserialized without a hitch.
- *
- * @param mixed $in Input
- * @param bool $force Force rebuild?
- * @return array lessphp cache structure
- */
- public function cachedCompile($in, $force = false) {
- // assume no root
- $root = null;
-
- if (is_string($in)) {
- $root = $in;
- } elseif (is_array($in) and isset($in['root'])) {
- if ($force or ! isset($in['files'])) {
- // If we are forcing a recompile or if for some reason the
- // structure does not contain any file information we should
- // specify the root to trigger a rebuild.
- $root = $in['root'];
- } elseif (isset($in['files']) and is_array($in['files'])) {
- foreach ($in['files'] as $fname => $ftime ) {
- if (!file_exists($fname) or filemtime($fname) > $ftime) {
- // One of the files we knew about previously has changed
- // so we should look at our incoming root again.
- $root = $in['root'];
- break;
- }
- }
- }
- } else {
- // TODO: Throw an exception? We got neither a string nor something
- // that looks like a compatible lessphp cache structure.
- return null;
- }
-
- if ($root !== null) {
- // If we have a root value which means we should rebuild.
- $out = array();
- $out['root'] = $root;
- $out['compiled'] = $this->compileFile($root);
- $out['files'] = $this->allParsedFiles();
- $out['updated'] = time();
- return $out;
- } else {
- // No changes, pass back the structure
- // we were given initially.
- return $in;
- }
-
- }
-
- // parse and compile buffer
- // This is deprecated
- public function parse($str = null, $initialVariables = null) {
- if (is_array($str)) {
- $initialVariables = $str;
- $str = null;
- }
-
- $oldVars = $this->registeredVars;
- if ($initialVariables !== null) {
- $this->setVariables($initialVariables);
- }
-
- if ($str == null) {
- if (empty($this->_parseFile)) {
- throw new exception("nothing to parse");
- }
-
- $out = $this->compileFile($this->_parseFile);
- } else {
- $out = $this->compile($str);
- }
-
- $this->registeredVars = $oldVars;
- return $out;
- }
-
- protected function makeParser($name) {
- $parser = new lessc_parser($this, $name);
- $parser->writeComments = $this->preserveComments;
-
- return $parser;
- }
-
- public function setFormatter($name) {
- $this->formatterName = $name;
- }
-
- protected function newFormatter() {
- $className = "lessc_formatter_lessjs";
- if (!empty($this->formatterName)) {
- if (!is_string($this->formatterName))
- return $this->formatterName;
- $className = "lessc_formatter_$this->formatterName";
- }
-
- return new $className;
- }
-
- public function setPreserveComments($preserve) {
- $this->preserveComments = $preserve;
- }
-
- public function registerFunction($name, $func) {
- $this->libFunctions[$name] = $func;
- }
-
- public function unregisterFunction($name) {
- unset($this->libFunctions[$name]);
- }
-
- public function setVariables($variables) {
- $this->registeredVars = array_merge($this->registeredVars, $variables);
- }
-
- public function unsetVariable($name) {
- unset($this->registeredVars[$name]);
- }
-
- public function setImportDir($dirs) {
- $this->importDir = (array)$dirs;
- }
-
- public function addImportDir($dir) {
- $this->importDir = (array)$this->importDir;
- $this->importDir[] = $dir;
- }
-
- public function allParsedFiles() {
- return $this->allParsedFiles;
- }
-
- public function addParsedFile($file) {
- $this->allParsedFiles[realpath($file)] = filemtime($file);
- }
-
- /**
- * Uses the current value of $this->count to show line and line number
- */
- public function throwError($msg = null) {
- if ($this->sourceLoc >= 0) {
- $this->sourceParser->throwError($msg, $this->sourceLoc);
- }
- throw new exception($msg);
- }
-
- // compile file $in to file $out if $in is newer than $out
- // returns true when it compiles, false otherwise
- public static function ccompile($in, $out, $less = null) {
- if ($less === null) {
- $less = new self;
- }
- return $less->checkedCompile($in, $out);
- }
-
- public static function cexecute($in, $force = false, $less = null) {
- if ($less === null) {
- $less = new self;
- }
- return $less->cachedCompile($in, $force);
- }
-
- static protected $cssColors = array(
- 'aliceblue' => '240,248,255',
- 'antiquewhite' => '250,235,215',
- 'aqua' => '0,255,255',
- 'aquamarine' => '127,255,212',
- 'azure' => '240,255,255',
- 'beige' => '245,245,220',
- 'bisque' => '255,228,196',
- 'black' => '0,0,0',
- 'blanchedalmond' => '255,235,205',
- 'blue' => '0,0,255',
- 'blueviolet' => '138,43,226',
- 'brown' => '165,42,42',
- 'burlywood' => '222,184,135',
- 'cadetblue' => '95,158,160',
- 'chartreuse' => '127,255,0',
- 'chocolate' => '210,105,30',
- 'coral' => '255,127,80',
- 'cornflowerblue' => '100,149,237',
- 'cornsilk' => '255,248,220',
- 'crimson' => '220,20,60',
- 'cyan' => '0,255,255',
- 'darkblue' => '0,0,139',
- 'darkcyan' => '0,139,139',
- 'darkgoldenrod' => '184,134,11',
- 'darkgray' => '169,169,169',
- 'darkgreen' => '0,100,0',
- 'darkgrey' => '169,169,169',
- 'darkkhaki' => '189,183,107',
- 'darkmagenta' => '139,0,139',
- 'darkolivegreen' => '85,107,47',
- 'darkorange' => '255,140,0',
- 'darkorchid' => '153,50,204',
- 'darkred' => '139,0,0',
- 'darksalmon' => '233,150,122',
- 'darkseagreen' => '143,188,143',
- 'darkslateblue' => '72,61,139',
- 'darkslategray' => '47,79,79',
- 'darkslategrey' => '47,79,79',
- 'darkturquoise' => '0,206,209',
- 'darkviolet' => '148,0,211',
- 'deeppink' => '255,20,147',
- 'deepskyblue' => '0,191,255',
- 'dimgray' => '105,105,105',
- 'dimgrey' => '105,105,105',
- 'dodgerblue' => '30,144,255',
- 'firebrick' => '178,34,34',
- 'floralwhite' => '255,250,240',
- 'forestgreen' => '34,139,34',
- 'fuchsia' => '255,0,255',
- 'gainsboro' => '220,220,220',
- 'ghostwhite' => '248,248,255',
- 'gold' => '255,215,0',
- 'goldenrod' => '218,165,32',
- 'gray' => '128,128,128',
- 'green' => '0,128,0',
- 'greenyellow' => '173,255,47',
- 'grey' => '128,128,128',
- 'honeydew' => '240,255,240',
- 'hotpink' => '255,105,180',
- 'indianred' => '205,92,92',
- 'indigo' => '75,0,130',
- 'ivory' => '255,255,240',
- 'khaki' => '240,230,140',
- 'lavender' => '230,230,250',
- 'lavenderblush' => '255,240,245',
- 'lawngreen' => '124,252,0',
- 'lemonchiffon' => '255,250,205',
- 'lightblue' => '173,216,230',
- 'lightcoral' => '240,128,128',
- 'lightcyan' => '224,255,255',
- 'lightgoldenrodyellow' => '250,250,210',
- 'lightgray' => '211,211,211',
- 'lightgreen' => '144,238,144',
- 'lightgrey' => '211,211,211',
- 'lightpink' => '255,182,193',
- 'lightsalmon' => '255,160,122',
- 'lightseagreen' => '32,178,170',
- 'lightskyblue' => '135,206,250',
- 'lightslategray' => '119,136,153',
- 'lightslategrey' => '119,136,153',
- 'lightsteelblue' => '176,196,222',
- 'lightyellow' => '255,255,224',
- 'lime' => '0,255,0',
- 'limegreen' => '50,205,50',
- 'linen' => '250,240,230',
- 'magenta' => '255,0,255',
- 'maroon' => '128,0,0',
- 'mediumaquamarine' => '102,205,170',
- 'mediumblue' => '0,0,205',
- 'mediumorchid' => '186,85,211',
- 'mediumpurple' => '147,112,219',
- 'mediumseagreen' => '60,179,113',
- 'mediumslateblue' => '123,104,238',
- 'mediumspringgreen' => '0,250,154',
- 'mediumturquoise' => '72,209,204',
- 'mediumvioletred' => '199,21,133',
- 'midnightblue' => '25,25,112',
- 'mintcream' => '245,255,250',
- 'mistyrose' => '255,228,225',
- 'moccasin' => '255,228,181',
- 'navajowhite' => '255,222,173',
- 'navy' => '0,0,128',
- 'oldlace' => '253,245,230',
- 'olive' => '128,128,0',
- 'olivedrab' => '107,142,35',
- 'orange' => '255,165,0',
- 'orangered' => '255,69,0',
- 'orchid' => '218,112,214',
- 'palegoldenrod' => '238,232,170',
- 'palegreen' => '152,251,152',
- 'paleturquoise' => '175,238,238',
- 'palevioletred' => '219,112,147',
- 'papayawhip' => '255,239,213',
- 'peachpuff' => '255,218,185',
- 'peru' => '205,133,63',
- 'pink' => '255,192,203',
- 'plum' => '221,160,221',
- 'powderblue' => '176,224,230',
- 'purple' => '128,0,128',
- 'red' => '255,0,0',
- 'rosybrown' => '188,143,143',
- 'royalblue' => '65,105,225',
- 'saddlebrown' => '139,69,19',
- 'salmon' => '250,128,114',
- 'sandybrown' => '244,164,96',
- 'seagreen' => '46,139,87',
- 'seashell' => '255,245,238',
- 'sienna' => '160,82,45',
- 'silver' => '192,192,192',
- 'skyblue' => '135,206,235',
- 'slateblue' => '106,90,205',
- 'slategray' => '112,128,144',
- 'slategrey' => '112,128,144',
- 'snow' => '255,250,250',
- 'springgreen' => '0,255,127',
- 'steelblue' => '70,130,180',
- 'tan' => '210,180,140',
- 'teal' => '0,128,128',
- 'thistle' => '216,191,216',
- 'tomato' => '255,99,71',
- 'transparent' => '0,0,0,0',
- 'turquoise' => '64,224,208',
- 'violet' => '238,130,238',
- 'wheat' => '245,222,179',
- 'white' => '255,255,255',
- 'whitesmoke' => '245,245,245',
- 'yellow' => '255,255,0',
- 'yellowgreen' => '154,205,50'
- );
-}
-
-// responsible for taking a string of LESS code and converting it into a
-// syntax tree
-class lessc_parser {
- static protected $nextBlockId = 0; // used to uniquely identify blocks
-
- static protected $precedence = array(
- '=<' => 0,
- '>=' => 0,
- '=' => 0,
- '<' => 0,
- '>' => 0,
-
- '+' => 1,
- '-' => 1,
- '*' => 2,
- '/' => 2,
- '%' => 2,
- );
-
- static protected $whitePattern;
- static protected $commentMulti;
-
- static protected $commentSingle = "//";
- static protected $commentMultiLeft = "/*";
- static protected $commentMultiRight = "*/";
-
- // regex string to match any of the operators
- static protected $operatorString;
-
- // these properties will supress division unless it's inside parenthases
- static protected $supressDivisionProps =
- array('/border-radius$/i', '/^font$/i');
-
- protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
- protected $lineDirectives = array("charset");
-
- /**
- * if we are in parens we can be more liberal with whitespace around
- * operators because it must evaluate to a single value and thus is less
- * ambiguous.
- *
- * Consider:
- * property1: 10 -5; // is two numbers, 10 and -5
- * property2: (10 -5); // should evaluate to 5
- */
- protected $inParens = false;
-
- // caches preg escaped literals
- static protected $literalCache = array();
-
- public function __construct($lessc, $sourceName = null) {
- $this->eatWhiteDefault = true;
- // reference to less needed for vPrefix, mPrefix, and parentSelector
- $this->lessc = $lessc;
-
- $this->sourceName = $sourceName; // name used for error messages
-
- $this->writeComments = false;
-
- if (!self::$operatorString) {
- self::$operatorString =
- '('.implode('|', array_map(array('lessc', 'preg_quote'),
- array_keys(self::$precedence))).')';
-
- $commentSingle = lessc::preg_quote(self::$commentSingle);
- $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
- $commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
-
- self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
- self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
- }
- }
-
- public function parse($buffer) {
- $this->count = 0;
- $this->line = 1;
-
- $this->env = null; // block stack
- $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
- $this->pushSpecialBlock("root");
- $this->eatWhiteDefault = true;
- $this->seenComments = array();
-
- // trim whitespace on head
- // if (preg_match('/^\s+/', $this->buffer, $m)) {
- // $this->line += substr_count($m[0], "\n");
- // $this->buffer = ltrim($this->buffer);
- // }
- $this->whitespace();
-
- // parse the entire file
- while (false !== $this->parseChunk());
-
- if ($this->count != strlen($this->buffer))
- $this->throwError();
-
- // TODO report where the block was opened
- if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
- throw new exception('parse error: unclosed block');
-
- return $this->env;
- }
-
- /**
- * Parse a single chunk off the head of the buffer and append it to the
- * current parse environment.
- * Returns false when the buffer is empty, or when there is an error.
- *
- * This function is called repeatedly until the entire document is
- * parsed.
- *
- * This parser is most similar to a recursive descent parser. Single
- * functions represent discrete grammatical rules for the language, and
- * they are able to capture the text that represents those rules.
- *
- * Consider the function lessc::keyword(). (all parse functions are
- * structured the same)
- *
- * The function takes a single reference argument. When calling the
- * function it will attempt to match a keyword on the head of the buffer.
- * If it is successful, it will place the keyword in the referenced
- * argument, advance the position in the buffer, and return true. If it
- * fails then it won't advance the buffer and it will return false.
- *
- * All of these parse functions are powered by lessc::match(), which behaves
- * the same way, but takes a literal regular expression. Sometimes it is
- * more convenient to use match instead of creating a new function.
- *
- * Because of the format of the functions, to parse an entire string of
- * grammatical rules, you can chain them together using &&.
- *
- * But, if some of the rules in the chain succeed before one fails, then
- * the buffer position will be left at an invalid state. In order to
- * avoid this, lessc::seek() is used to remember and set buffer positions.
- *
- * Before parsing a chain, use $s = $this->seek() to remember the current
- * position into $s. Then if a chain fails, use $this->seek($s) to
- * go back where we started.
- */
- protected function parseChunk() {
- if (empty($this->buffer)) return false;
- $s = $this->seek();
-
- if ($this->whitespace()) {
- return true;
- }
-
- // setting a property
- if ($this->keyword($key) && $this->assign() &&
- $this->propertyValue($value, $key) && $this->end())
- {
- $this->append(array('assign', $key, $value), $s);
- return true;
- } else {
- $this->seek($s);
- }
-
-
- // look for special css blocks
- if ($this->literal('@', false)) {
- $this->count--;
-
- // media
- if ($this->literal('@media')) {
- if (($this->mediaQueryList($mediaQueries) || true)
- && $this->literal('{'))
- {
- $media = $this->pushSpecialBlock("media");
- $media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
- return true;
- } else {
- $this->seek($s);
- return false;
- }
- }
-
- if ($this->literal("@", false) && $this->keyword($dirName)) {
- if ($this->isDirective($dirName, $this->blockDirectives)) {
- if (($this->openString("{", $dirValue, null, array(";")) || true) &&
- $this->literal("{"))
- {
- $dir = $this->pushSpecialBlock("directive");
- $dir->name = $dirName;
- if (isset($dirValue)) $dir->value = $dirValue;
- return true;
- }
- } elseif ($this->isDirective($dirName, $this->lineDirectives)) {
- if ($this->propertyValue($dirValue) && $this->end()) {
- $this->append(array("directive", $dirName, $dirValue));
- return true;
- }
- }
- }
-
- $this->seek($s);
- }
-
- // setting a variable
- if ($this->variable($var) && $this->assign() &&
- $this->propertyValue($value) && $this->end())
- {
- $this->append(array('assign', $var, $value), $s);
- return true;
- } else {
- $this->seek($s);
- }
-
- if ($this->import($importValue)) {
- $this->append($importValue, $s);
- return true;
- }
-
- // opening parametric mixin
- if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
- ($this->guards($guards) || true) &&
- $this->literal('{'))
- {
- $block = $this->pushBlock($this->fixTags(array($tag)));
- $block->args = $args;
- $block->isVararg = $isVararg;
- if (!empty($guards)) $block->guards = $guards;
- return true;
- } else {
- $this->seek($s);
- }
-
- // opening a simple block
- if ($this->tags($tags) && $this->literal('{', false)) {
- $tags = $this->fixTags($tags);
- $this->pushBlock($tags);
- return true;
- } else {
- $this->seek($s);
- }
-
- // closing a block
- if ($this->literal('}', false)) {
- try {
- $block = $this->pop();
- } catch (exception $e) {
- $this->seek($s);
- $this->throwError($e->getMessage());
- }
-
- $hidden = false;
- if (is_null($block->type)) {
- $hidden = true;
- if (!isset($block->args)) {
- foreach ($block->tags as $tag) {
- if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
- $hidden = false;
- break;
- }
- }
- }
-
- foreach ($block->tags as $tag) {
- if (is_string($tag)) {
- $this->env->children[$tag][] = $block;
- }
- }
- }
-
- if (!$hidden) {
- $this->append(array('block', $block), $s);
- }
-
- // this is done here so comments aren't bundled into he block that
- // was just closed
- $this->whitespace();
- return true;
- }
-
- // mixin
- if ($this->mixinTags($tags) &&
- ($this->argumentDef($argv, $isVararg) || true) &&
- ($this->keyword($suffix) || true) && $this->end())
- {
- $tags = $this->fixTags($tags);
- $this->append(array('mixin', $tags, $argv, $suffix), $s);
- return true;
- } else {
- $this->seek($s);
- }
-
- // spare ;
- if ($this->literal(';')) return true;
-
- return false; // got nothing, throw error
- }
-
- protected function isDirective($dirname, $directives) {
- // TODO: cache pattern in parser
- $pattern = implode("|",
- array_map(array("lessc", "preg_quote"), $directives));
- $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
-
- return preg_match($pattern, $dirname);
- }
-
- protected function fixTags($tags) {
- // move @ tags out of variable namespace
- foreach ($tags as &$tag) {
- if ($tag{0} == $this->lessc->vPrefix)
- $tag[0] = $this->lessc->mPrefix;
- }
- return $tags;
- }
-
- // a list of expressions
- protected function expressionList(&$exps) {
- $values = array();
-
- while ($this->expression($exp)) {
- $values[] = $exp;
- }
-
- if (count($values) == 0) return false;
-
- $exps = lessc::compressList($values, ' ');
- return true;
- }
-
- /**
- * Attempt to consume an expression.
- * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
- */
- protected function expression(&$out) {
- if ($this->value($lhs)) {
- $out = $this->expHelper($lhs, 0);
-
- // look for / shorthand
- if (!empty($this->env->supressedDivision)) {
- unset($this->env->supressedDivision);
- $s = $this->seek();
- if ($this->literal("/") && $this->value($rhs)) {
- $out = array("list", "",
- array($out, array("keyword", "/"), $rhs));
- } else {
- $this->seek($s);
- }
- }
-
- return true;
- }
- return false;
- }
-
- /**
- * recursively parse infix equation with $lhs at precedence $minP
- */
- protected function expHelper($lhs, $minP) {
- $this->inExp = true;
- $ss = $this->seek();
-
- while (true) {
- $whiteBefore = isset($this->buffer[$this->count - 1]) &&
- ctype_space($this->buffer[$this->count - 1]);
-
- // If there is whitespace before the operator, then we require
- // whitespace after the operator for it to be an expression
- $needWhite = $whiteBefore && !$this->inParens;
-
- if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
- if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
- foreach (self::$supressDivisionProps as $pattern) {
- if (preg_match($pattern, $this->env->currentProperty)) {
- $this->env->supressedDivision = true;
- break 2;
- }
- }
- }
-
-
- $whiteAfter = isset($this->buffer[$this->count - 1]) &&
- ctype_space($this->buffer[$this->count - 1]);
-
- if (!$this->value($rhs)) break;
-
- // peek for next operator to see what to do with rhs
- if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
- $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
- }
-
- $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
- $ss = $this->seek();
-
- continue;
- }
-
- break;
- }
-
- $this->seek($ss);
-
- return $lhs;
- }
-
- // consume a list of values for a property
- public function propertyValue(&$value, $keyName = null) {
- $values = array();
-
- if ($keyName !== null) $this->env->currentProperty = $keyName;
-
- $s = null;
- while ($this->expressionList($v)) {
- $values[] = $v;
- $s = $this->seek();
- if (!$this->literal(',')) break;
- }
-
- if ($s) $this->seek($s);
-
- if ($keyName !== null) unset($this->env->currentProperty);
-
- if (count($values) == 0) return false;
-
- $value = lessc::compressList($values, ', ');
- return true;
- }
-
- protected function parenValue(&$out) {
- $s = $this->seek();
-
- // speed shortcut
- if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
- return false;
- }
-
- $inParens = $this->inParens;
- if ($this->literal("(") &&
- ($this->inParens = true) && $this->expression($exp) &&
- $this->literal(")"))
- {
- $out = $exp;
- $this->inParens = $inParens;
- return true;
- } else {
- $this->inParens = $inParens;
- $this->seek($s);
- }
-
- return false;
- }
-
- // a single value
- protected function value(&$value) {
- $s = $this->seek();
-
- // speed shortcut
- if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
- // negation
- if ($this->literal("-", false) &&
- (($this->variable($inner) && $inner = array("variable", $inner)) ||
- $this->unit($inner) ||
- $this->parenValue($inner)))
- {
- $value = array("unary", "-", $inner);
- return true;
- } else {
- $this->seek($s);
- }
- }
-
- if ($this->parenValue($value)) return true;
- if ($this->unit($value)) return true;
- if ($this->color($value)) return true;
- if ($this->func($value)) return true;
- if ($this->string($value)) return true;
-
- if ($this->keyword($word)) {
- $value = array('keyword', $word);
- return true;
- }
-
- // try a variable
- if ($this->variable($var)) {
- $value = array('variable', $var);
- return true;
- }
-
- // unquote string (should this work on any type?
- if ($this->literal("~") && $this->string($str)) {
- $value = array("escape", $str);
- return true;
- } else {
- $this->seek($s);
- }
-
- // css hack: \0
- if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
- $value = array('keyword', '\\'.$m[1]);
- return true;
- } else {
- $this->seek($s);
- }
-
- return false;
- }
-
- // an import statement
- protected function import(&$out) {
- if (!$this->literal('@import')) return false;
-
- // @import "something.css" media;
- // @import url("something.css") media;
- // @import url(something.css) media;
-
- if ($this->propertyValue($value)) {
- $out = array("import", $value);
- return true;
- }
- }
-
- protected function mediaQueryList(&$out) {
- if ($this->genericList($list, "mediaQuery", ",", false)) {
- $out = $list[2];
- return true;
- }
- return false;
- }
-
- protected function mediaQuery(&$out) {
- $s = $this->seek();
-
- $expressions = null;
- $parts = array();
-
- if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
- $prop = array("mediaType");
- if (isset($only)) $prop[] = "only";
- if (isset($not)) $prop[] = "not";
- $prop[] = $mediaType;
- $parts[] = $prop;
- } else {
- $this->seek($s);
- }
-
-
- if (!empty($mediaType) && !$this->literal("and")) {
- // ~
- } else {
- $this->genericList($expressions, "mediaExpression", "and", false);
- if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
- }
-
- if (count($parts) == 0) {
- $this->seek($s);
- return false;
- }
-
- $out = $parts;
- return true;
- }
-
- protected function mediaExpression(&$out) {
- $s = $this->seek();
- $value = null;
- if ($this->literal("(") &&
- $this->keyword($feature) &&
- ($this->literal(":") && $this->expression($value) || true) &&
- $this->literal(")"))
- {
- $out = array("mediaExp", $feature);
- if ($value) $out[] = $value;
- return true;
- } elseif ($this->variable($variable)) {
- $out = array('variable', $variable);
- return true;
- }
-
- $this->seek($s);
- return false;
- }
-
- // an unbounded string stopped by $end
- protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
- $oldWhite = $this->eatWhiteDefault;
- $this->eatWhiteDefault = false;
-
- $stop = array("'", '"', "@{", $end);
- $stop = array_map(array("lessc", "preg_quote"), $stop);
- // $stop[] = self::$commentMulti;
-
- if (!is_null($rejectStrs)) {
- $stop = array_merge($stop, $rejectStrs);
- }
-
- $patt = '(.*?)('.implode("|", $stop).')';
-
- $nestingLevel = 0;
-
- $content = array();
- while ($this->match($patt, $m, false)) {
- if (!empty($m[1])) {
- $content[] = $m[1];
- if ($nestingOpen) {
- $nestingLevel += substr_count($m[1], $nestingOpen);
- }
- }
-
- $tok = $m[2];
-
- $this->count-= strlen($tok);
- if ($tok == $end) {
- if ($nestingLevel == 0) {
- break;
- } else {
- $nestingLevel--;
- }
- }
-
- if (($tok == "'" || $tok == '"') && $this->string($str)) {
- $content[] = $str;
- continue;
- }
-
- if ($tok == "@{" && $this->interpolation($inter)) {
- $content[] = $inter;
- continue;
- }
-
- if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
- break;
- }
-
- $content[] = $tok;
- $this->count+= strlen($tok);
- }
-
- $this->eatWhiteDefault = $oldWhite;
-
- if (count($content) == 0) return false;
-
- // trim the end
- if (is_string(end($content))) {
- $content[count($content) - 1] = rtrim(end($content));
- }
-
- $out = array("string", "", $content);
- return true;
- }
-
- protected function string(&$out) {
- $s = $this->seek();
- if ($this->literal('"', false)) {
- $delim = '"';
- } elseif ($this->literal("'", false)) {
- $delim = "'";
- } else {
- return false;
- }
-
- $content = array();
-
- // look for either ending delim , escape, or string interpolation
- $patt = '([^\n]*?)(@\{|\\\\|' .
- lessc::preg_quote($delim).')';
-
- $oldWhite = $this->eatWhiteDefault;
- $this->eatWhiteDefault = false;
-
- while ($this->match($patt, $m, false)) {
- $content[] = $m[1];
- if ($m[2] == "@{") {
- $this->count -= strlen($m[2]);
- if ($this->interpolation($inter, false)) {
- $content[] = $inter;
- } else {
- $this->count += strlen($m[2]);
- $content[] = "@{"; // ignore it
- }
- } elseif ($m[2] == '\\') {
- $content[] = $m[2];
- if ($this->literal($delim, false)) {
- $content[] = $delim;
- }
- } else {
- $this->count -= strlen($delim);
- break; // delim
- }
- }
-
- $this->eatWhiteDefault = $oldWhite;
-
- if ($this->literal($delim)) {
- $out = array("string", $delim, $content);
- return true;
- }
-
- $this->seek($s);
- return false;
- }
-
- protected function interpolation(&$out) {
- $oldWhite = $this->eatWhiteDefault;
- $this->eatWhiteDefault = true;
-
- $s = $this->seek();
- if ($this->literal("@{") &&
- $this->openString("}", $interp, null, array("'", '"', ";")) &&
- $this->literal("}", false))
- {
- $out = array("interpolate", $interp);
- $this->eatWhiteDefault = $oldWhite;
- if ($this->eatWhiteDefault) $this->whitespace();
- return true;
- }
-
- $this->eatWhiteDefault = $oldWhite;
- $this->seek($s);
- return false;
- }
-
- protected function unit(&$unit) {
- // speed shortcut
- if (isset($this->buffer[$this->count])) {
- $char = $this->buffer[$this->count];
- if (!ctype_digit($char) && $char != ".") return false;
- }
-
- if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
- $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
- return true;
- }
- return false;
- }
-
- // a # color
- protected function color(&$out) {
- if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
- if (strlen($m[1]) > 7) {
- $out = array("string", "", array($m[1]));
- } else {
- $out = array("raw_color", $m[1]);
- }
- return true;
- }
-
- return false;
- }
-
- // consume an argument definition list surrounded by ()
- // each argument is a variable name with optional value
- // or at the end a ... or a variable named followed by ...
- // arguments are separated by , unless a ; is in the list, then ; is the
- // delimiter.
- protected function argumentDef(&$args, &$isVararg) {
- $s = $this->seek();
- if (!$this->literal('(')) return false;
-
- $values = array();
- $delim = ",";
- $method = "expressionList";
-
- $isVararg = false;
- while (true) {
- if ($this->literal("...")) {
- $isVararg = true;
- break;
- }
-
- if ($this->$method($value)) {
- if ($value[0] == "variable") {
- $arg = array("arg", $value[1]);
- $ss = $this->seek();
-
- if ($this->assign() && $this->$method($rhs)) {
- $arg[] = $rhs;
- } else {
- $this->seek($ss);
- if ($this->literal("...")) {
- $arg[0] = "rest";
- $isVararg = true;
- }
- }
-
- $values[] = $arg;
- if ($isVararg) break;
- continue;
- } else {
- $values[] = array("lit", $value);
- }
- }
-
-
- if (!$this->literal($delim)) {
- if ($delim == "," && $this->literal(";")) {
- // found new delim, convert existing args
- $delim = ";";
- $method = "propertyValue";
-
- // transform arg list
- if (isset($values[1])) { // 2 items
- $newList = array();
- foreach ($values as $i => $arg) {
- switch($arg[0]) {
- case "arg":
- if ($i) {
- $this->throwError("Cannot mix ; and , as delimiter types");
- }
- $newList[] = $arg[2];
- break;
- case "lit":
- $newList[] = $arg[1];
- break;
- case "rest":
- $this->throwError("Unexpected rest before semicolon");
- }
- }
-
- $newList = array("list", ", ", $newList);
-
- switch ($values[0][0]) {
- case "arg":
- $newArg = array("arg", $values[0][1], $newList);
- break;
- case "lit":
- $newArg = array("lit", $newList);
- break;
- }
-
- } elseif ($values) { // 1 item
- $newArg = $values[0];
- }
-
- if ($newArg) {
- $values = array($newArg);
- }
- } else {
- break;
- }
- }
- }
-
- if (!$this->literal(')')) {
- $this->seek($s);
- return false;
- }
-
- $args = $values;
-
- return true;
- }
-
- // consume a list of tags
- // this accepts a hanging delimiter
- protected function tags(&$tags, $simple = false, $delim = ',') {
- $tags = array();
- while ($this->tag($tt, $simple)) {
- $tags[] = $tt;
- if (!$this->literal($delim)) break;
- }
- if (count($tags) == 0) return false;
-
- return true;
- }
-
- // list of tags of specifying mixin path
- // optionally separated by > (lazy, accepts extra >)
- protected function mixinTags(&$tags) {
- $tags = array();
- while ($this->tag($tt, true)) {
- $tags[] = $tt;
- $this->literal(">");
- }
-
- if (count($tags) == 0) return false;
-
- return true;
- }
-
- // a bracketed value (contained within in a tag definition)
- protected function tagBracket(&$parts, &$hasExpression) {
- // speed shortcut
- if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
- return false;
- }
-
- $s = $this->seek();
-
- $hasInterpolation = false;
-
- if ($this->literal("[", false)) {
- $attrParts = array("[");
- // keyword, string, operator
- while (true) {
- if ($this->literal("]", false)) {
- $this->count--;
- break; // get out early
- }
-
- if ($this->match('\s+', $m)) {
- $attrParts[] = " ";
- continue;
- }
- if ($this->string($str)) {
- // escape parent selector, (yuck)
- foreach ($str[2] as &$chunk) {
- $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
- }
-
- $attrParts[] = $str;
- $hasInterpolation = true;
- continue;
- }
-
- if ($this->keyword($word)) {
- $attrParts[] = $word;
- continue;
- }
-
- if ($this->interpolation($inter, false)) {
- $attrParts[] = $inter;
- $hasInterpolation = true;
- continue;
- }
-
- // operator, handles attr namespace too
- if ($this->match('[|-~\$\*\^=]+', $m)) {
- $attrParts[] = $m[0];
- continue;
- }
-
- break;
- }
-
- if ($this->literal("]", false)) {
- $attrParts[] = "]";
- foreach ($attrParts as $part) {
- $parts[] = $part;
- }
- $hasExpression = $hasExpression || $hasInterpolation;
- return true;
- }
- $this->seek($s);
- }
-
- $this->seek($s);
- return false;
- }
-
- // a space separated list of selectors
- protected function tag(&$tag, $simple = false) {
- if ($simple)
- $chars = '^@,:;{}\][>\(\) "\'';
- else
- $chars = '^@,;{}["\'';
-
- $s = $this->seek();
-
- $hasExpression = false;
- $parts = array();
- while ($this->tagBracket($parts, $hasExpression));
-
- $oldWhite = $this->eatWhiteDefault;
- $this->eatWhiteDefault = false;
-
- while (true) {
- if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
- $parts[] = $m[1];
- if ($simple) break;
-
- while ($this->tagBracket($parts, $hasExpression));
- continue;
- }
-
- if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
- if ($this->interpolation($interp)) {
- $hasExpression = true;
- $interp[2] = true; // don't unescape
- $parts[] = $interp;
- continue;
- }
-
- if ($this->literal("@")) {
- $parts[] = "@";
- continue;
- }
- }
-
- if ($this->unit($unit)) { // for keyframes
- $parts[] = $unit[1];
- $parts[] = $unit[2];
- continue;
- }
-
- break;
- }
-
- $this->eatWhiteDefault = $oldWhite;
- if (!$parts) {
- $this->seek($s);
- return false;
- }
-
- if ($hasExpression) {
- $tag = array("exp", array("string", "", $parts));
- } else {
- $tag = trim(implode($parts));
- }
-
- $this->whitespace();
- return true;
- }
-
- // a css function
- protected function func(&$func) {
- $s = $this->seek();
-
- if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
- $fname = $m[1];
-
- $sPreArgs = $this->seek();
-
- $args = array();
- while (true) {
- $ss = $this->seek();
- // this ugly nonsense is for ie filter properties
- if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
- $args[] = array("string", "", array($name, "=", $value));
- } else {
- $this->seek($ss);
- if ($this->expressionList($value)) {
- $args[] = $value;
- }
- }
-
- if (!$this->literal(',')) break;
- }
- $args = array('list', ',', $args);
-
- if ($this->literal(')')) {
- $func = array('function', $fname, $args);
- return true;
- } elseif ($fname == 'url') {
- // couldn't parse and in url? treat as string
- $this->seek($sPreArgs);
- if ($this->openString(")", $string) && $this->literal(")")) {
- $func = array('function', $fname, $string);
- return true;
- }
- }
- }
-
- $this->seek($s);
- return false;
- }
-
- // consume a less variable
- protected function variable(&$name) {
- $s = $this->seek();
- if ($this->literal($this->lessc->vPrefix, false) &&
- ($this->variable($sub) || $this->keyword($name)))
- {
- if (!empty($sub)) {
- $name = array('variable', $sub);
- } else {
- $name = $this->lessc->vPrefix.$name;
- }
- return true;
- }
-
- $name = null;
- $this->seek($s);
- return false;
- }
-
- /**
- * Consume an assignment operator
- * Can optionally take a name that will be set to the current property name
- */
- protected function assign($name = null) {
- if ($name) $this->currentProperty = $name;
- return $this->literal(':') || $this->literal('=');
- }
-
- // consume a keyword
- protected function keyword(&$word) {
- if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
- $word = $m[1];
- return true;
- }
- return false;
- }
-
- // consume an end of statement delimiter
- protected function end() {
- if ($this->literal(';', false)) {
- return true;
- } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
- // if there is end of file or a closing block next then we don't need a ;
- return true;
- }
- return false;
- }
-
- protected function guards(&$guards) {
- $s = $this->seek();
-
- if (!$this->literal("when")) {
- $this->seek($s);
- return false;
- }
-
- $guards = array();
-
- while ($this->guardGroup($g)) {
- $guards[] = $g;
- if (!$this->literal(",")) break;
- }
-
- if (count($guards) == 0) {
- $guards = null;
- $this->seek($s);
- return false;
- }
-
- return true;
- }
-
- // a bunch of guards that are and'd together
- // TODO rename to guardGroup
- protected function guardGroup(&$guardGroup) {
- $s = $this->seek();
- $guardGroup = array();
- while ($this->guard($guard)) {
- $guardGroup[] = $guard;
- if (!$this->literal("and")) break;
- }
-
- if (count($guardGroup) == 0) {
- $guardGroup = null;
- $this->seek($s);
- return false;
- }
-
- return true;
- }
-
- protected function guard(&$guard) {
- $s = $this->seek();
- $negate = $this->literal("not");
-
- if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
- $guard = $exp;
- if ($negate) $guard = array("negate", $guard);
- return true;
- }
-
- $this->seek($s);
- return false;
- }
-
- /* raw parsing functions */
-
- protected function literal($what, $eatWhitespace = null) {
- if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
-
- // shortcut on single letter
- if (!isset($what[1]) && isset($this->buffer[$this->count])) {
- if ($this->buffer[$this->count] == $what) {
- if (!$eatWhitespace) {
- $this->count++;
- return true;
- }
- // goes below...
- } else {
- return false;
- }
- }
-
- if (!isset(self::$literalCache[$what])) {
- self::$literalCache[$what] = lessc::preg_quote($what);
- }
-
- return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
- }
-
- protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
- $s = $this->seek();
- $items = array();
- while ($this->$parseItem($value)) {
- $items[] = $value;
- if ($delim) {
- if (!$this->literal($delim)) break;
- }
- }
-
- if (count($items) == 0) {
- $this->seek($s);
- return false;
- }
-
- if ($flatten && count($items) == 1) {
- $out = $items[0];
- } else {
- $out = array("list", $delim, $items);
- }
-
- return true;
- }
-
-
- // advance counter to next occurrence of $what
- // $until - don't include $what in advance
- // $allowNewline, if string, will be used as valid char set
- protected function to($what, &$out, $until = false, $allowNewline = false) {
- if (is_string($allowNewline)) {
- $validChars = $allowNewline;
- } else {
- $validChars = $allowNewline ? "." : "[^\n]";
- }
- if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
- if ($until) $this->count -= strlen($what); // give back $what
- $out = $m[1];
- return true;
- }
-
- // try to match something on head of buffer
- protected function match($regex, &$out, $eatWhitespace = null) {
- if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
-
- $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
- if (preg_match($r, $this->buffer, $out, null, $this->count)) {
- $this->count += strlen($out[0]);
- if ($eatWhitespace && $this->writeComments) $this->whitespace();
- return true;
- }
- return false;
- }
-
- // match some whitespace
- protected function whitespace() {
- if ($this->writeComments) {
- $gotWhite = false;
- while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
- if (isset($m[1]) && empty($this->seenComments[$this->count])) {
- $this->append(array("comment", $m[1]));
- $this->seenComments[$this->count] = true;
- }
- $this->count += strlen($m[0]);
- $gotWhite = true;
- }
- return $gotWhite;
- } else {
- $this->match("", $m);
- return strlen($m[0]) > 0;
- }
- }
-
- // match something without consuming it
- protected function peek($regex, &$out = null, $from=null) {
- if (is_null($from)) $from = $this->count;
- $r = '/'.$regex.'/Ais';
- $result = preg_match($r, $this->buffer, $out, null, $from);
-
- return $result;
- }
-
- // seek to a spot in the buffer or return where we are on no argument
- protected function seek($where = null) {
- if ($where === null) return $this->count;
- else $this->count = $where;
- return true;
- }
-
- /* misc functions */
-
- public function throwError($msg = "parse error", $count = null) {
- $count = is_null($count) ? $this->count : $count;
-
- $line = $this->line +
- substr_count(substr($this->buffer, 0, $count), "\n");
-
- if (!empty($this->sourceName)) {
- $loc = "$this->sourceName on line $line";
- } else {
- $loc = "line: $line";
- }
-
- // TODO this depends on $this->count
- if ($this->peek("(.*?)(\n|$)", $m, $count)) {
- throw new exception("$msg: failed at `$m[1]` $loc");
- } else {
- throw new exception("$msg: $loc");
- }
- }
-
- protected function pushBlock($selectors=null, $type=null) {
- $b = new stdclass;
- $b->parent = $this->env;
-
- $b->type = $type;
- $b->id = self::$nextBlockId++;
-
- $b->isVararg = false; // TODO: kill me from here
- $b->tags = $selectors;
-
- $b->props = array();
- $b->children = array();
-
- $this->env = $b;
- return $b;
- }
-
- // push a block that doesn't multiply tags
- protected function pushSpecialBlock($type) {
- return $this->pushBlock(null, $type);
- }
-
- // append a property to the current block
- protected function append($prop, $pos = null) {
- if ($pos !== null) $prop[-1] = $pos;
- $this->env->props[] = $prop;
- }
-
- // pop something off the stack
- protected function pop() {
- $old = $this->env;
- $this->env = $this->env->parent;
- return $old;
- }
-
- // remove comments from $text
- // todo: make it work for all functions, not just url
- protected function removeComments($text) {
- $look = array(
- 'url(', '//', '/*', '"', "'"
- );
-
- $out = '';
- $min = null;
- while (true) {
- // find the next item
- foreach ($look as $token) {
- $pos = strpos($text, $token);
- if ($pos !== false) {
- if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
- }
- }
-
- if (is_null($min)) break;
-
- $count = $min[1];
- $skip = 0;
- $newlines = 0;
- switch ($min[0]) {
- case 'url(':
- if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
- $count += strlen($m[0]) - strlen($min[0]);
- break;
- case '"':
- case "'":
- if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
- $count += strlen($m[0]) - 1;
- break;
- case '//':
- $skip = strpos($text, "\n", $count);
- if ($skip === false) $skip = strlen($text) - $count;
- else $skip -= $count;
- break;
- case '/*':
- if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
- $skip = strlen($m[0]);
- $newlines = substr_count($m[0], "\n");
- }
- break;
- }
-
- if ($skip == 0) $count += strlen($min[0]);
-
- $out .= substr($text, 0, $count).str_repeat("\n", $newlines);
- $text = substr($text, $count + $skip);
-
- $min = null;
- }
-
- return $out.$text;
- }
-
-}
-
-class lessc_formatter_classic {
- public $indentChar = " ";
-
- public $break = "\n";
- public $open = " {";
- public $close = "}";
- public $selectorSeparator = ", ";
- public $assignSeparator = ":";
-
- public $openSingle = " { ";
- public $closeSingle = " }";
-
- public $disableSingle = false;
- public $breakSelectors = false;
-
- public $compressColors = false;
-
- public function __construct() {
- $this->indentLevel = 0;
- }
-
- public function indentStr($n = 0) {
- return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
- }
-
- public function property($name, $value) {
- return $name . $this->assignSeparator . $value . ";";
- }
-
- protected function isEmpty($block) {
- if (empty($block->lines)) {
- foreach ($block->children as $child) {
- if (!$this->isEmpty($child)) return false;
- }
-
- return true;
- }
- return false;
- }
-
- public function block($block) {
- if ($this->isEmpty($block)) return;
-
- $inner = $pre = $this->indentStr();
-
- $isSingle = !$this->disableSingle &&
- is_null($block->type) && count($block->lines) == 1;
-
- if (!empty($block->selectors)) {
- $this->indentLevel++;
-
- if ($this->breakSelectors) {
- $selectorSeparator = $this->selectorSeparator . $this->break . $pre;
- } else {
- $selectorSeparator = $this->selectorSeparator;
- }
-
- echo $pre .
- implode($selectorSeparator, $block->selectors);
- if ($isSingle) {
- echo $this->openSingle;
- $inner = "";
- } else {
- echo $this->open . $this->break;
- $inner = $this->indentStr();
- }
-
- }
-
- if (!empty($block->lines)) {
- $glue = $this->break.$inner;
- echo $inner . implode($glue, $block->lines);
- if (!$isSingle && !empty($block->children)) {
- echo $this->break;
- }
- }
-
- foreach ($block->children as $child) {
- $this->block($child);
- }
-
- if (!empty($block->selectors)) {
- if (!$isSingle && empty($block->children)) echo $this->break;
-
- if ($isSingle) {
- echo $this->closeSingle . $this->break;
- } else {
- echo $pre . $this->close . $this->break;
- }
-
- $this->indentLevel--;
- }
- }
-}
-
-class lessc_formatter_compressed extends lessc_formatter_classic {
- public $disableSingle = true;
- public $open = "{";
- public $selectorSeparator = ",";
- public $assignSeparator = ":";
- public $break = "";
- public $compressColors = true;
-
- public function indentStr($n = 0) {
- return "";
- }
-}
-
-class lessc_formatter_lessjs extends lessc_formatter_classic {
- public $disableSingle = true;
- public $breakSelectors = true;
- public $assignSeparator = ": ";
- public $selectorSeparator = ",";
-}
-
-
diff --git a/includes/libs/normal/UtfNormal.php b/includes/libs/normal/UtfNormal.php
new file mode 100644
index 00000000..c9c05a07
--- /dev/null
+++ b/includes/libs/normal/UtfNormal.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Unicode normalization routines
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
+
+/**
+ * @defgroup UtfNormal UtfNormal
+ */
+
+use UtfNormal\Validator;
+
+/**
+ * Unicode normalization routines for working with UTF-8 strings.
+ * Currently assumes that input strings are valid UTF-8!
+ *
+ * Not as fast as I'd like, but should be usable for most purposes.
+ * UtfNormal::toNFC() will bail early if given ASCII text or text
+ * it can quickly determine is already normalized.
+ *
+ * All functions can be called static.
+ *
+ * See description of forms at http://www.unicode.org/reports/tr15/
+ *
+ * @deprecated since 1.25, use UtfNormal\Validator directly
+ * @ingroup UtfNormal
+ */
+class UtfNormal {
+ /**
+ * The ultimate convenience function! Clean up invalid UTF-8 sequences,
+ * and convert to normal form C, canonical composition.
+ *
+ * Fast return for pure ASCII strings; some lesser optimizations for
+ * strings containing only known-good characters. Not as fast as toNFC().
+ *
+ * @param string $string a UTF-8 string
+ * @return string a clean, shiny, normalized UTF-8 string
+ */
+ static function cleanUp( $string ) {
+ return Validator::cleanUp( $string );
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form C, canonical composition.
+ * Fast return for pure ASCII strings; some lesser optimizations for
+ * strings containing only known-good characters.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form C
+ */
+ static function toNFC( $string ) {
+ return Validator::toNFC( $string );
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form D, canonical decomposition.
+ * Fast return for pure ASCII strings.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form D
+ */
+ static function toNFD( $string ) {
+ return Validator::toNFD( $string );
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form KC, compatibility composition.
+ * This may cause irreversible information loss, use judiciously.
+ * Fast return for pure ASCII strings.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form KC
+ */
+ static function toNFKC( $string ) {
+ return Validator::toNFKC( $string );
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form KD, compatibility decomposition.
+ * This may cause irreversible information loss, use judiciously.
+ * Fast return for pure ASCII strings.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form KD
+ */
+ static function toNFKD( $string ) {
+ return Validator::toNFKD( $string );
+ }
+
+ /**
+ * Returns true if the string is _definitely_ in NFC.
+ * Returns false if not or uncertain.
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return bool
+ */
+ static function quickIsNFC( $string ) {
+ return Validator::quickIsNFC( $string );
+ }
+
+ /**
+ * Returns true if the string is _definitely_ in NFC.
+ * Returns false if not or uncertain.
+ * @param string $string a UTF-8 string, altered on output to be valid UTF-8 safe for XML.
+ * @return bool
+ */
+ static function quickIsNFCVerify( &$string ) {
+ return Validator::quickIsNFCVerify( $string );
+ }
+}
diff --git a/includes/normal/UtfNormalDefines.php b/includes/libs/normal/UtfNormalDefines.php
index 18d89f6d..b8e44c77 100644
--- a/includes/normal/UtfNormalDefines.php
+++ b/includes/libs/normal/UtfNormalDefines.php
@@ -1,10 +1,8 @@
<?php
/**
- * Some constant definitions for the unicode normalization module.
- *
- * Note: these constants must all be resolvable at compile time by HipHop,
- * since this file will not be executed during request startup for a compiled
- * MediaWiki.
+ * Backwards-compatability constants which are now provided by the
+ * UtfNormal library. They are hardcoded here since they are needed
+ * before the composer autoloader is initialized.
*
* This 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,53 +23,164 @@
* @ingroup UtfNormal
*/
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_FIRST', 0xac00 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_LBASE', 0x1100 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_VBASE', 0x1161 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_TBASE', 0x11a7 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_LCOUNT', 19 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_VCOUNT', 21 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_TCOUNT', 28 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_NCOUNT', UNICODE_HANGUL_VCOUNT * UNICODE_HANGUL_TCOUNT );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_LEND', UNICODE_HANGUL_LBASE + UNICODE_HANGUL_LCOUNT - 1 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_VEND', UNICODE_HANGUL_VBASE + UNICODE_HANGUL_VCOUNT - 1 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_HANGUL_TEND', UNICODE_HANGUL_TBASE + UNICODE_HANGUL_TCOUNT - 1 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_SURROGATE_FIRST', 0xd800 );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_SURROGATE_LAST', 0xdfff );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_MAX', 0x10ffff );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UNICODE_REPLACEMENT', 0xfffd );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_FIRST', "\xea\xb0\x80" /*codepointToUtf8( UNICODE_HANGUL_FIRST )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_LAST', "\xed\x9e\xa3" /*codepointToUtf8( UNICODE_HANGUL_LAST )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_LBASE', "\xe1\x84\x80" /*codepointToUtf8( UNICODE_HANGUL_LBASE )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_VBASE', "\xe1\x85\xa1" /*codepointToUtf8( UNICODE_HANGUL_VBASE )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_TBASE', "\xe1\x86\xa7" /*codepointToUtf8( UNICODE_HANGUL_TBASE )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_LEND', "\xe1\x84\x92" /*codepointToUtf8( UNICODE_HANGUL_LEND )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_VEND', "\xe1\x85\xb5" /*codepointToUtf8( UNICODE_HANGUL_VEND )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HANGUL_TEND', "\xe1\x87\x82" /*codepointToUtf8( UNICODE_HANGUL_TEND )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_SURROGATE_FIRST', "\xed\xa0\x80" /*codepointToUtf8( UNICODE_SURROGATE_FIRST )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_SURROGATE_LAST', "\xed\xbf\xbf" /*codepointToUtf8( UNICODE_SURROGATE_LAST )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_MAX', "\xf4\x8f\xbf\xbf" /*codepointToUtf8( UNICODE_MAX )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_REPLACEMENT', "\xef\xbf\xbd" /*codepointToUtf8( UNICODE_REPLACEMENT )*/ );
#define( 'UTF8_REPLACEMENT', '!' );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_OVERLONG_A', "\xc1\xbf" );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_OVERLONG_B', "\xe0\x9f\xbf" );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_OVERLONG_C', "\xf0\x8f\xbf\xbf" );
# These two ranges are illegal
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_FDD0', "\xef\xb7\x90" /*codepointToUtf8( 0xfdd0 )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_FDEF', "\xef\xb7\xaf" /*codepointToUtf8( 0xfdef )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_FFFE', "\xef\xbf\xbe" /*codepointToUtf8( 0xfffe )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_FFFF', "\xef\xbf\xbf" /*codepointToUtf8( 0xffff )*/ );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_HEAD', false );
+/**
+ * @deprecated since 1.25, use UtfNormal\Constants instead
+ */
define( 'UTF8_TAIL', true );
diff --git a/includes/normal/UtfNormalUtil.php b/includes/libs/normal/UtfNormalUtil.php
index 6c925dfa..ad9a2b9a 100644
--- a/includes/normal/UtfNormalUtil.php
+++ b/includes/libs/normal/UtfNormalUtil.php
@@ -25,39 +25,19 @@
* @ingroup UtfNormal
*/
+
+use UtfNormal\Utils;
/**
* Return UTF-8 sequence for a given Unicode code point.
- * May die if fed out of range data.
*
* @param $codepoint Integer:
* @return String
+ * @throws InvalidArgumentException if fed out of range data.
* @public
+ * @deprecated since 1.25, use UtfNormal\Utils directly
*/
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 );
- }
-
- echo "Asked for code outside of range ($codepoint)\n";
- die( -1 );
+ return Utils::codepointToUtf8( $codepoint );
}
/**
@@ -67,22 +47,19 @@ function codepointToUtf8( $codepoint ) {
*
* @param $sequence String
* @return String
+ * @throws InvalidArgumentException if fed out of range data.
* @private
+ * @deprecated since 1.25, use UtfNormal\Utils directly
*/
function hexSequenceToUtf8( $sequence ) {
- $utf = '';
- foreach ( explode( ' ', $sequence ) as $hex ) {
- $n = hexdec( $hex );
- $utf .= codepointToUtf8( $n );
- }
-
- return $utf;
+ return Utils::hexSequenceToUtf8( $sequence );
}
/**
* Take a UTF-8 string and return a space-separated series of hex
* numbers representing Unicode code points. For debugging.
*
+ * @fixme this is private but extensions + maint scripts are using it
* @param string $str UTF-8 string.
* @return string
* @private
@@ -90,7 +67,7 @@ function hexSequenceToUtf8( $sequence ) {
function utf8ToHexSequence( $str ) {
$buf = '';
foreach ( preg_split( '//u', $str, -1, PREG_SPLIT_NO_EMPTY ) as $cp ) {
- $buf .= sprintf( '%04x ', utf8ToCodepoint( $cp ) );
+ $buf .= sprintf( '%04x ', UtfNormal\Utils::utf8ToCodepoint( $cp ) );
}
return rtrim( $buf );
@@ -103,39 +80,10 @@ function utf8ToHexSequence( $str ) {
* @param $char String
* @return Integer
* @public
+ * @deprecated since 1.25, use UtfNormal\Utils directly
*/
function utf8ToCodepoint( $char ) {
- # Find the length
- $z = ord( $char[0] );
- if ( $z & 0x80 ) {
- $length = 0;
- while ( $z & 0x80 ) {
- $length++;
- $z <<= 1;
- }
- } else {
- $length = 1;
- }
-
- if ( $length != strlen( $char ) ) {
- return false;
- }
-
- if ( $length == 1 ) {
- return ord( $char );
- }
-
- # Mask off the length-determining bits and shift back to the original location
- $z &= 0xff;
- $z >>= $length;
-
- # Add in the free bits from subsequent bytes
- for ( $i = 1; $i < $length; $i++ ) {
- $z <<= 6;
- $z |= ord( $char[$i] ) & 0x3f;
- }
-
- return $z;
+ return Utils::utf8ToCodepoint( $char );
}
/**
@@ -144,11 +92,8 @@ function utf8ToCodepoint( $char ) {
* @param string $string string to be escaped.
* @return String: escaped string.
* @public
+ * @deprecated since 1.25, use UtfNormal\Utils directly
*/
function escapeSingleString( $string ) {
- return strtr( $string,
- array(
- '\\' => '\\\\',
- '\'' => '\\\''
- ) );
+ return Utils::escapeSingleString( $string );
}
diff --git a/includes/objectcache/APCBagOStuff.php b/includes/libs/objectcache/APCBagOStuff.php
index 4cbb32df..eaf11557 100644
--- a/includes/objectcache/APCBagOStuff.php
+++ b/includes/libs/objectcache/APCBagOStuff.php
@@ -27,11 +27,6 @@
* @ingroup Cache
*/
class APCBagOStuff extends BagOStuff {
- /**
- * @param string $key
- * @param int $casToken [optional]
- * @return mixed
- */
public function get( $key, &$casToken = null ) {
$val = apc_fetch( $key );
@@ -48,12 +43,6 @@ class APCBagOStuff extends BagOStuff {
return $val;
}
- /**
- * @param string $key
- * @param mixed $value
- * @param int $exptime
- * @return bool
- */
public function set( $key, $value, $exptime = 0 ) {
if ( !$this->isInteger( $value ) ) {
$value = serialize( $value );
@@ -64,40 +53,12 @@ class APCBagOStuff extends BagOStuff {
return true;
}
- /**
- * @param mixed $casToken
- * @param string $key
- * @param mixed $value
- * @param int $exptime
- * @return bool
- */
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
- // APC's CAS functions only work on integers
- throw new MWException( "CAS is not implemented in " . __CLASS__ );
- }
-
- /**
- * @param string $key
- * @param int $time
- * @return bool
- */
- public function delete( $key, $time = 0 ) {
+ public function delete( $key ) {
apc_delete( $key );
return true;
}
- /**
- * @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
- */
- public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
- return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
- }
-
public function incr( $key, $value = 1 ) {
return apc_inc( $key, $value );
}
diff --git a/includes/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php
index 1978c3ea..0b791e5a 100644
--- a/includes/objectcache/BagOStuff.php
+++ b/includes/libs/objectcache/BagOStuff.php
@@ -28,6 +28,10 @@
* @defgroup Cache Cache
*/
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
/**
* interface is intended to be more or less compatible with
* the PHP memcached client.
@@ -40,17 +44,38 @@
*
* @ingroup Cache
*/
-abstract class BagOStuff {
+abstract class BagOStuff implements LoggerAwareInterface {
private $debugMode = false;
protected $lastError = self::ERR_NONE;
+ /**
+ * @var LoggerInterface
+ */
+ protected $logger;
+
/** 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
+ public function __construct( array $params = array() ) {
+ if ( isset( $params['logger'] ) ) {
+ $this->setLogger( $params['logger'] );
+ } else {
+ $this->setLogger( new NullLogger() );
+ }
+ }
+
+ /**
+ * @param LoggerInterface $logger
+ * @return null
+ */
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
/**
* @param bool $bool
*/
@@ -58,9 +83,6 @@ abstract class BagOStuff {
$this->debugMode = $bool;
}
- /* *** THE GUTS OF THE OPERATION *** */
- /* Override these with functional things in subclasses */
-
/**
* Get an item with the given key. Returns false if it does not exist.
* @param string $key
@@ -79,22 +101,11 @@ abstract class BagOStuff {
abstract public function set( $key, $value, $exptime = 0 );
/**
- * Check and set an item.
- * @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
- */
- abstract public function cas( $casToken, $key, $value, $exptime = 0 );
-
- /**
* Delete an item.
* @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
*/
- abstract public function delete( $key, $time = 0 );
+ abstract public function delete( $key );
/**
* Merge changes into the existing cache value (possibly creating a new one).
@@ -102,29 +113,34 @@ abstract class BagOStuff {
* and takes the arguments: (this BagOStuff object, cache key, current value).
*
* @param string $key
- * @param Closure $callback Callback method to be executed
+ * @param callable $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
*/
- public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
- return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !is_callable( $callback ) ) {
+ throw new Exception( "Got invalid callback." );
+ }
+
+ return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
}
/**
* @see BagOStuff::merge()
*
* @param string $key
- * @param Closure $callback Callback method to be executed
+ * @param callable $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
*/
- protected function mergeViaCas( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
+ protected function mergeViaCas( $key, $callback, $exptime = 0, $attempts = 10 ) {
do {
$casToken = null; // passed by reference
- $currentValue = $this->get( $key, $casToken ); // get the old value
- $value = $callback( $this, $key, $currentValue ); // derive the new value
+ $currentValue = $this->get( $key, $casToken );
+ // Derive the new value from the old value
+ $value = call_user_func( $callback, $this, $key, $currentValue );
if ( $value === false ) {
$success = true; // do nothing
@@ -141,21 +157,35 @@ abstract class BagOStuff {
}
/**
+ * Check and set an item
+ *
+ * @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
+ */
+ protected function cas( $casToken, $key, $value, $exptime = 0 ) {
+ throw new Exception( "CAS is not implemented in " . __CLASS__ );
+ }
+
+ /**
* @see BagOStuff::merge()
*
* @param string $key
- * @param Closure $callback Callback method to be executed
+ * @param callable $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
*/
- protected function mergeViaLock( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
+ protected function mergeViaLock( $key, $callback, $exptime = 0, $attempts = 10 ) {
if ( !$this->lock( $key, 6 ) ) {
return false;
}
- $currentValue = $this->get( $key ); // get the old value
- $value = $callback( $this, $key, $currentValue ); // derive the new value
+ $currentValue = $this->get( $key );
+ // Derive the new value from the old value
+ $value = call_user_func( $callback, $this, $key, $currentValue );
if ( $value === false ) {
$success = true; // do nothing
@@ -173,13 +203,14 @@ abstract class BagOStuff {
/**
* @param string $key
- * @param int $timeout [optional]
+ * @param int $timeout Lock wait timeout [optional]
+ * @param int $expiry Lock expiry [optional]
* @return bool Success
*/
- public function lock( $key, $timeout = 6 ) {
+ public function lock( $key, $timeout = 6, $expiry = 6 ) {
$this->clearLastError();
$timestamp = microtime( true ); // starting UNIX timestamp
- if ( $this->add( "{$key}:lock", 1, $timeout ) ) {
+ if ( $this->add( "{$key}:lock", 1, $expiry ) ) {
return true;
} elseif ( $this->getLastError() ) {
return false;
@@ -191,18 +222,18 @@ abstract class BagOStuff {
$locked = false; // lock acquired
$attempts = 0; // failed attempts
do {
- if ( ++$attempts >= 3 && $sleep <= 1e6 ) {
+ if ( ++$attempts >= 3 && $sleep <= 5e5 ) {
// Exponentially back off after failed attempts to avoid network spam.
// About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts.
$sleep *= 2;
}
usleep( $sleep ); // back off
$this->clearLastError();
- $locked = $this->add( "{$key}:lock", 1, $timeout );
+ $locked = $this->add( "{$key}:lock", 1, $expiry );
if ( $this->getLastError() ) {
return false;
}
- } while ( !$locked );
+ } while ( !$locked && ( microtime( true ) - $timestamp ) < $timeout );
return $locked;
}
@@ -355,10 +386,11 @@ abstract class BagOStuff {
/**
* @param string $text
*/
- public function debug( $text ) {
+ protected function debug( $text ) {
if ( $this->debugMode ) {
- $class = get_class( $this );
- wfDebug( "$class debug: $text\n" );
+ $this->logger->debug( "{class} debug: $text", array(
+ 'class' => get_class( $this ),
+ ) );
}
}
diff --git a/includes/objectcache/EmptyBagOStuff.php b/includes/libs/objectcache/EmptyBagOStuff.php
index 9595b83c..4ccf2707 100644
--- a/includes/objectcache/EmptyBagOStuff.php
+++ b/includes/libs/objectcache/EmptyBagOStuff.php
@@ -27,54 +27,19 @@
* @ingroup Cache
*/
class EmptyBagOStuff extends BagOStuff {
-
- /**
- * @param string $key
- * @param mixed $casToken [optional]
- * @return bool
- */
- function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null ) {
return false;
}
- /**
- * @param string $key
- * @param mixed $value
- * @param int $exp
- * @return bool
- */
- function set( $key, $value, $exp = 0 ) {
+ public function set( $key, $value, $exp = 0 ) {
return true;
}
- /**
- * @param mixed $casToken
- * @param string $key
- * @param mixed $value
- * @param int $exp
- * @return bool
- */
- function cas( $casToken, $key, $value, $exp = 0 ) {
+ public function delete( $key ) {
return true;
}
- /**
- * @param string $key
- * @param int $time
- * @return bool
- */
- function delete( $key, $time = 0 ) {
- return true;
- }
-
- /**
- * @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
- */
- public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
- return true;
+ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
+ return true; // faster
}
}
diff --git a/includes/objectcache/HashBagOStuff.php b/includes/libs/objectcache/HashBagOStuff.php
index 6e50a8c3..2c8b05a5 100644
--- a/includes/objectcache/HashBagOStuff.php
+++ b/includes/libs/objectcache/HashBagOStuff.php
@@ -31,14 +31,11 @@ class HashBagOStuff extends BagOStuff {
/** @var array */
protected $bag;
- function __construct() {
+ function __construct( $params = array() ) {
+ parent::__construct( $params );
$this->bag = array();
}
- /**
- * @param string $key
- * @return bool
- */
protected function expire( $key ) {
$et = $this->bag[$key][1];
@@ -51,12 +48,7 @@ class HashBagOStuff extends BagOStuff {
return true;
}
- /**
- * @param string $key
- * @param mixed $casToken [optional]
- * @return bool|mixed
- */
- function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null ) {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
@@ -65,43 +57,17 @@ class HashBagOStuff extends BagOStuff {
return false;
}
- $casToken = serialize( $this->bag[$key][0] );
+ $casToken = $this->bag[$key][0];
return $this->bag[$key][0];
}
- /**
- * @param string $key
- * @param mixed $value
- * @param int $exptime
- * @return bool
- */
- function set( $key, $value, $exptime = 0 ) {
+ public function set( $key, $value, $exptime = 0 ) {
$this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) );
return true;
}
- /**
- * @param mixed $casToken
- * @param string $key
- * @param mixed $value
- * @param int $exptime
- * @return bool
- */
- function cas( $casToken, $key, $value, $exptime = 0 ) {
- if ( serialize( $this->get( $key ) ) === $casToken ) {
- return $this->set( $key, $value, $exptime );
- }
-
- return false;
- }
-
- /**
- * @param string $key
- * @param int $time
- * @return bool
- */
- function delete( $key, $time = 0 ) {
+ function delete( $key ) {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
@@ -110,4 +76,12 @@ class HashBagOStuff extends BagOStuff {
return true;
}
+
+ public function lock( $key, $timeout = 6, $expiry = 6 ) {
+ return true;
+ }
+
+ function unlock( $key ) {
+ return true;
+ }
}
diff --git a/includes/objectcache/WinCacheBagOStuff.php b/includes/libs/objectcache/WinCacheBagOStuff.php
index 78a512ce..53625746 100644
--- a/includes/objectcache/WinCacheBagOStuff.php
+++ b/includes/libs/objectcache/WinCacheBagOStuff.php
@@ -73,7 +73,7 @@ class WinCacheBagOStuff extends BagOStuff {
* @param int $exptime Expiration time
* @return bool
*/
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ protected function cas( $casToken, $key, $value, $exptime = 0 ) {
return wincache_ucache_cas( $key, $casToken, serialize( $value ) );
}
@@ -81,12 +81,19 @@ 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
* @return bool
*/
- public function delete( $key, $time = 0 ) {
+ public function delete( $key ) {
wincache_ucache_delete( $key );
return true;
}
+
+ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !is_callable( $callback ) ) {
+ throw new Exception( "Got invalid callback." );
+ }
+
+ return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ }
}
diff --git a/includes/objectcache/XCacheBagOStuff.php b/includes/libs/objectcache/XCacheBagOStuff.php
index 8e2a160d..cfee9236 100644
--- a/includes/objectcache/XCacheBagOStuff.php
+++ b/includes/libs/objectcache/XCacheBagOStuff.php
@@ -69,44 +69,16 @@ class XCacheBagOStuff extends BagOStuff {
}
/**
- * @param mixed $casToken
- * @param string $key
- * @param mixed $value
- * @param int $exptime
- * @return bool
- */
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
- // Can't find any documentation on xcache cas
- throw new MWException( "CAS is not implemented in " . __CLASS__ );
- }
-
- /**
* Remove a value from the XCache object cache
*
* @param string $key Cache key
- * @param int $time Not used in this implementation
* @return bool
*/
- public function delete( $key, $time = 0 ) {
+ public function delete( $key ) {
xcache_unset( $key );
return true;
}
- /**
- * Merge an item.
- * XCache does not seem to support any way of performing CAS - this however will
- * provide a way to perform CAS-like functionality.
- *
- * @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
- */
- public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
- return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
- }
-
public function incr( $key, $value = 1 ) {
return xcache_inc( $key, $value );
}
diff --git a/includes/libs/replacers/DoubleReplacer.php b/includes/libs/replacers/DoubleReplacer.php
new file mode 100644
index 00000000..fed023b1
--- /dev/null
+++ b/includes/libs/replacers/DoubleReplacer.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
+ */
+
+/**
+ * Class to perform secondary replacement within each replacement string
+ */
+class DoubleReplacer extends Replacer {
+ /**
+ * @param mixed $from
+ * @param mixed $to
+ * @param int $index
+ */
+ public function __construct( $from, $to, $index = 0 ) {
+ $this->from = $from;
+ $this->to = $to;
+ $this->index = $index;
+ }
+
+ /**
+ * @param array $matches
+ * @return mixed
+ */
+ public function replace( array $matches ) {
+ return str_replace( $this->from, $this->to, $matches[$this->index] );
+ }
+}
diff --git a/includes/libs/replacers/HashtableReplacer.php b/includes/libs/replacers/HashtableReplacer.php
new file mode 100644
index 00000000..b3c219d4
--- /dev/null
+++ b/includes/libs/replacers/HashtableReplacer.php
@@ -0,0 +1,44 @@
+<?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 to perform replacement based on a simple hashtable lookup
+ */
+class HashtableReplacer extends Replacer {
+ private $table, $index;
+
+ /**
+ * @param array $table
+ * @param int $index
+ */
+ public function __construct( $table, $index = 0 ) {
+ $this->table = $table;
+ $this->index = $index;
+ }
+
+ /**
+ * @param array $matches
+ * @return mixed
+ */
+ public function replace( array $matches ) {
+ return $this->table[$matches[$this->index]];
+ }
+}
+
diff --git a/includes/libs/replacers/RegexlikeReplacer.php b/includes/libs/replacers/RegexlikeReplacer.php
new file mode 100644
index 00000000..2b1fa740
--- /dev/null
+++ b/includes/libs/replacers/RegexlikeReplacer.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Class to replace regex matches with a string similar to that used in preg_replace()
+ */
+class RegexlikeReplacer extends Replacer {
+ private $r;
+
+ /**
+ * @param string $r
+ */
+ public function __construct( $r ) {
+ $this->r = $r;
+ }
+
+ /**
+ * @param array $matches
+ * @return string
+ */
+ public function replace( array $matches ) {
+ $pairs = array();
+ foreach ( $matches as $i => $match ) {
+ $pairs["\$$i"] = $match;
+ }
+
+ return strtr( $this->r, $pairs );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderFilePageModule.php b/includes/libs/replacers/Replacer.php
index 8c7fbe76..f4850bf6 100644
--- a/includes/resourceloader/ResourceLoaderFilePageModule.php
+++ b/includes/libs/replacers/Replacer.php
@@ -1,7 +1,5 @@
<?php
/**
- * Resource loader module for MediaWiki:Filepage.css
- *
* This 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
@@ -21,17 +19,20 @@
*/
/**
- * ResourceLoader definition for MediaWiki:Filepage.css
+ * Base class for "replacers", objects used in preg_replace_callback() and
+ * StringUtils::delimiterReplaceCallback()
*/
-class ResourceLoaderFilePageModule extends ResourceLoaderWikiModule {
-
+abstract class Replacer {
/**
- * @param ResourceLoaderContext $context
* @return array
*/
- protected function getPages( ResourceLoaderContext $context ) {
- return array(
- 'MediaWiki:Filepage.css' => array( 'type' => 'style' ),
- );
+ public function cb() {
+ return array( &$this, 'replace' );
}
+
+ /**
+ * @param array $matches
+ * @return string
+ */
+ abstract public function replace( array $matches );
}
diff --git a/includes/libs/virtualrest/ParsoidVirtualRESTService.php b/includes/libs/virtualrest/ParsoidVirtualRESTService.php
new file mode 100644
index 00000000..32a27f79
--- /dev/null
+++ b/includes/libs/virtualrest/ParsoidVirtualRESTService.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Virtual HTTP service client for Parsoid
+ *
+ * This 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
+ */
+
+/**
+ * Virtual REST service for Parsoid
+ * @since 1.25
+ */
+class ParsoidVirtualRESTService extends VirtualRESTService {
+ /**
+ * Example requests:
+ * GET /local/v1/page/$title/html/$oldid
+ * * $oldid is optional
+ * POST /local/v1/transform/html/to/wikitext/$title/$oldid
+ * * body: array( 'html' => ... )
+ * * $title and $oldid are optional
+ * POST /local/v1/transform/wikitext/to/html/$title
+ * * body: array( 'wikitext' => ... ) or array( 'wikitext' => ..., 'body' => true/false )
+ * * $title is optional
+ * @param array $params Key/value map
+ * - url : Parsoid server URL
+ * - prefix : Parsoid prefix for this wiki
+ * - timeout : Parsoid timeout (optional)
+ * - forwardCookies : Cookies to forward to Parsoid, or false. (optional)
+ * - HTTPProxy : Parsoid HTTP proxy (optional)
+ */
+ public function __construct( array $params ) {
+ // for backwards compatibility:
+ if ( isset( $params['URL'] ) ) {
+ $params['url'] = $params['URL'];
+ unset( $params['URL'] );
+ }
+ parent::__construct( $params );
+ }
+
+ public function onRequests( array $reqs, Closure $idGeneratorFunc ) {
+ $result = array();
+ foreach ( $reqs as $key => $req ) {
+ $parts = explode( '/', $req['url'] );
+
+ list(
+ $targetWiki, // 'local'
+ $version, // 'v1'
+ $reqType // 'page' or 'transform'
+ ) = $parts;
+
+ if ( $targetWiki !== 'local' ) {
+ throw new Exception( "Only 'local' target wiki is currently supported" );
+ } elseif ( $version !== 'v1' ) {
+ throw new Exception( "Only version 1 exists" );
+ } elseif ( $reqType !== 'page' && $reqType !== 'transform' ) {
+ throw new Exception( "Request type must be either 'page' or 'transform'" );
+ }
+
+ $req['url'] = $this->params['url'] . '/' . urlencode( $this->params['prefix'] ) . '/';
+
+ if ( $reqType === 'page' ) {
+ $title = $parts[3];
+ if ( $parts[4] !== 'html' ) {
+ throw new Exception( "Only 'html' output format is currently supported" );
+ }
+ if ( isset( $parts[5] ) ) {
+ $req['url'] .= $title . '?oldid=' . $parts[5];
+ } else {
+ $req['url'] .= $title;
+ }
+ } elseif ( $reqType === 'transform' ) {
+ if ( $parts[4] !== 'to' ) {
+ throw new Exception( "Part index 4 is not 'to'" );
+ }
+
+ if ( isset( $parts[6] ) ) {
+ $req['url'] .= $parts[6];
+ }
+
+ if ( $parts[3] === 'html' & $parts[5] === 'wikitext' ) {
+ if ( !isset( $req['body']['html'] ) ) {
+ throw new Exception( "You must set an 'html' body key for this request" );
+ }
+ if ( isset( $parts[7] ) ) {
+ $req['body']['oldid'] = $parts[7];
+ }
+ } elseif ( $parts[3] == 'wikitext' && $parts[5] == 'html' ) {
+ if ( !isset( $req['body']['wikitext'] ) ) {
+ throw new Exception( "You must set a 'wikitext' body key for this request" );
+ }
+ $req['body']['wt'] = $req['body']['wikitext'];
+ unset( $req['body']['wikitext'] );
+ } else {
+ throw new Exception( "Transformation unsupported" );
+ }
+ }
+
+ if ( isset( $this->params['HTTPProxy'] ) && $this->params['HTTPProxy'] ) {
+ $req['proxy'] = $this->params['HTTPProxy'];
+ }
+ if ( isset( $this->params['timeout'] ) ) {
+ $req['reqTimeout'] = $this->params['timeout'];
+ }
+
+ // Forward cookies
+ if ( isset( $this->params['forwardCookies'] ) ) {
+ $req['headers']['Cookie'] = $this->params['forwardCookies'];
+ }
+
+ $result[$key] = $req;
+ }
+ return $result;
+ }
+}
diff --git a/includes/libs/virtualrest/RestbaseVirtualRESTService.php b/includes/libs/virtualrest/RestbaseVirtualRESTService.php
new file mode 100644
index 00000000..8fe5b921
--- /dev/null
+++ b/includes/libs/virtualrest/RestbaseVirtualRESTService.php
@@ -0,0 +1,177 @@
+<?php
+/**
+ * Virtual HTTP service client for Restbase
+ *
+ * This 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
+ */
+
+/**
+ * Virtual REST service for Restbase
+ * @since 1.25
+ */
+class RestbaseVirtualRESTService extends VirtualRESTService {
+ /**
+ * Example requests:
+ * GET /local/v1/page/{title}/html{/revision}
+ * POST /local/v1/transform/html/to/wikitext{/title}{/revision}
+ * * body: array( 'html' => ... )
+ * POST /local/v1/transform/wikitext/to/html{/title}{/revision}
+ * * body: array( 'wikitext' => ... ) or array( 'wikitext' => ..., 'bodyOnly' => true/false )
+ *
+ * @param array $params Key/value map
+ * - url : Restbase server URL
+ * - domain : Wiki domain to use
+ * - timeout : request timeout in seconds (optional)
+ * - forwardCookies : cookies to forward to Restbase/Parsoid (as a Cookie
+ * header string) or false (optional)
+ * Note: forwardCookies will in the future be a boolean
+ * only, signifing request cookies should be forwarded
+ * to the service; the current state is due to the way
+ * VE handles this particular parameter
+ * - HTTPProxy : HTTP proxy to use (optional)
+ * - parsoidCompat : whether to parse URL as if they were meant for Parsoid
+ * boolean (optional)
+ */
+ public function __construct( array $params ) {
+ // set up defaults and merge them with the given params
+ $mparams = array_merge( array(
+ 'url' => 'http://localhost:7231',
+ 'domain' => 'localhost',
+ 'timeout' => 100,
+ 'forwardCookies' => false,
+ 'HTTPProxy' => null,
+ 'parsoidCompat' => false
+ ), $params );
+ // ensure the correct domain format
+ $mparams['domain'] = preg_replace(
+ '/^(https?:\/\/)?([^\/:]+?)(\/|:\d+\/?)?$/',
+ '$2',
+ $mparams['domain']
+ );
+ parent::__construct( $mparams );
+ }
+
+ public function onRequests( array $reqs, Closure $idGenFunc ) {
+
+ if ( $this->params['parsoidCompat'] ) {
+ return $this->onParsoidRequests( $reqs, $idGenFunc );
+ }
+
+ $result = array();
+ foreach ( $reqs as $key => $req ) {
+ // replace /local/ with the current domain
+ $req['url'] = preg_replace( '/^\/local\//', '/' . $this->params['domain'] . '/', $req['url'] );
+ // and prefix it with the service URL
+ $req['url'] = $this->params['url'] . $req['url'];
+ // set the appropriate proxy, timeout and headers
+ if ( $this->params['HTTPProxy'] ) {
+ $req['proxy'] = $this->params['HTTPProxy'];
+ }
+ if ( $this->params['timeout'] != null ) {
+ $req['reqTimeout'] = $this->params['timeout'];
+ }
+ if ( $this->params['forwardCookies'] ) {
+ $req['headers']['Cookie'] = $this->params['forwardCookies'];
+ }
+ $result[$key] = $req;
+ }
+
+ return $result;
+
+ }
+
+ /**
+ * Remaps Parsoid requests to Restbase paths
+ */
+ public function onParsoidRequests( array $reqs, Closure $idGeneratorFunc ) {
+
+ $result = array();
+ foreach ( $reqs as $key => $req ) {
+ $parts = explode( '/', $req['url'] );
+ list(
+ $targetWiki, // 'local'
+ $version, // 'v1'
+ $reqType // 'page' or 'transform'
+ ) = $parts;
+ if ( $targetWiki !== 'local' ) {
+ throw new Exception( "Only 'local' target wiki is currently supported" );
+ } elseif ( $reqType !== 'page' && $reqType !== 'transform' ) {
+ throw new Exception( "Request type must be either 'page' or 'transform'" );
+ }
+ $req['url'] = $this->params['url'] . '/' . $this->params['domain'] . '/v1/' . $reqType . '/';
+ if ( $reqType === 'page' ) {
+ $title = $parts[3];
+ if ( $parts[4] !== 'html' ) {
+ throw new Exception( "Only 'html' output format is currently supported" );
+ }
+ $req['url'] .= 'html/' . $title;
+ if ( isset( $parts[5] ) ) {
+ $req['url'] .= '/' . $parts[5];
+ } elseif ( isset( $req['query']['oldid'] ) && $req['query']['oldid'] ) {
+ $req['url'] .= '/' . $req['query']['oldid'];
+ unset( $req['query']['oldid'] );
+ }
+ } elseif ( $reqType === 'transform' ) {
+ // from / to transform
+ $req['url'] .= $parts[3] . '/to/' . $parts[5];
+ // the title
+ if ( isset( $parts[6] ) ) {
+ $req['url'] .= '/' . $parts[6];
+ }
+ // revision id
+ if ( isset( $parts[7] ) ) {
+ $req['url'] .= '/' . $parts[7];
+ } elseif ( isset( $req['body']['oldid'] ) && $req['body']['oldid'] ) {
+ $req['url'] .= '/' . $req['body']['oldid'];
+ unset( $req['body']['oldid'] );
+ }
+ if ( $parts[4] !== 'to' ) {
+ throw new Exception( "Part index 4 is not 'to'" );
+ }
+ if ( $parts[3] === 'html' & $parts[5] === 'wikitext' ) {
+ if ( !isset( $req['body']['html'] ) ) {
+ throw new Exception( "You must set an 'html' body key for this request" );
+ }
+ } elseif ( $parts[3] == 'wikitext' && $parts[5] == 'html' ) {
+ if ( !isset( $req['body']['wikitext'] ) ) {
+ throw new Exception( "You must set a 'wikitext' body key for this request" );
+ }
+ if ( isset( $req['body']['body'] ) ) {
+ $req['body']['bodyOnly'] = $req['body']['body'];
+ unset( $req['body']['body'] );
+ }
+ } else {
+ throw new Exception( "Transformation unsupported" );
+ }
+ }
+ // set the appropriate proxy, timeout and headers
+ if ( $this->params['HTTPProxy'] ) {
+ $req['proxy'] = $this->params['HTTPProxy'];
+ }
+ if ( $this->params['timeout'] != null ) {
+ $req['reqTimeout'] = $this->params['timeout'];
+ }
+ if ( $this->params['forwardCookies'] ) {
+ $req['headers']['Cookie'] = $this->params['forwardCookies'];
+ }
+ $result[$key] = $req;
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/includes/libs/virtualrest/VirtualRESTServiceClient.php b/includes/libs/virtualrest/VirtualRESTServiceClient.php
index 2d21d3cf..e8bb38d8 100644
--- a/includes/libs/virtualrest/VirtualRESTServiceClient.php
+++ b/includes/libs/virtualrest/VirtualRESTServiceClient.php
@@ -125,17 +125,17 @@ class VirtualRESTServiceClient {
* - 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
+ * - error : 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
+ * @param array $req Virtual HTTP request maps
* @return array Response array for request
*/
public function run( array $req ) {
- $req = $this->runMulti( array( $req ) );
- return $req[0]['response'];
+ $responses = $this->runMulti( array( $req ) );
+ return $responses[0];
}
/**
@@ -146,14 +146,15 @@ class VirtualRESTServiceClient {
* - 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
+ * - error : 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>
+ * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $responses[0];
* </code>
*
- * @param array $req Map of Virtual HTTP request arrays
+ * @param array $reqs Map of Virtual HTTP request maps
* @return array $reqs Map of corresponding response values with the same keys/order
+ * @throws Exception
*/
public function runMulti( array $reqs ) {
foreach ( $reqs as $index => &$req ) {
@@ -207,6 +208,9 @@ class VirtualRESTServiceClient {
if ( ++$rounds > 5 ) { // sanity
throw new Exception( "Too many replacement rounds detected. Aborting." );
}
+ // Track requests executed this round that have a prefix/service.
+ // Note that this also includes requests where 'response' was forced.
+ $checkReqIndexesByPrefix = array();
// 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
@@ -219,7 +223,7 @@ class VirtualRESTServiceClient {
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
+ // Replacement request that will convert to original requests
$newReplaceReqsByService[$prefix][$index] = $req;
}
if ( isset( $req['response'] ) ) {
@@ -231,6 +235,7 @@ class VirtualRESTServiceClient {
// Original or mangled request included
$executeReqs[$index] = $req;
}
+ $checkReqIndexesByPrefix[$prefix][$index] = 1;
}
}
// Update index of requests to inspect for replacement
@@ -245,12 +250,12 @@ class VirtualRESTServiceClient {
// 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.
+ // forced by setting 'response' rather than actually be sent over the wire.
$newReplaceReqsByService = array();
- foreach ( $replaceReqsByService as $prefix => $servReqs ) {
+ foreach ( $checkReqIndexesByPrefix as $prefix => $servReqIndexes ) {
$service = $this->instances[$prefix];
- // Only the request copies stored in $doneReqs actually have the response
- $servReqs = array_intersect_key( $doneReqs, $servReqs );
+ // $doneReqs actually has the requests (with 'response' set)
+ $servReqs = array_intersect_key( $doneReqs, $servReqIndexes );
foreach ( $service->onResponses( $servReqs, $idFunc ) as $index => $req ) {
// Services use unique IDs for replacement requests
if ( isset( $servReqs[$index] ) || isset( $origPending[$index] ) ) {
diff --git a/includes/logging/BlockLogFormatter.php b/includes/logging/BlockLogFormatter.php
new file mode 100644
index 00000000..07ef24b4
--- /dev/null
+++ b/includes/logging/BlockLogFormatter.php
@@ -0,0 +1,224 @@
+<?php
+/**
+ * Formatter for block 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
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.25
+ */
+
+/**
+ * This class formats block log entries.
+ *
+ * @since 1.25
+ */
+class BlockLogFormatter extends LogFormatter {
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+
+ $title = $this->entry->getTarget();
+ if ( substr( $title->getText(), 0, 1 ) === '#' ) {
+ // autoblock - no user link possible
+ $params[2] = $title->getText();
+ $params[3] = ''; // no user name for gender use
+ } else {
+ // Create a user link for the blocked
+ $username = $title->getText();
+ // @todo Store the user identifier in the parameters
+ // to make this faster for future log entries
+ $targetUser = User::newFromName( $username, false );
+ $params[2] = Message::rawParam( $this->makeUserLink( $targetUser, Linker::TOOL_LINKS_NOBLOCK ) );
+ $params[3] = $username; // plain user name for gender use
+ }
+
+ $subtype = $this->entry->getSubtype();
+ if ( $subtype === 'block' || $subtype === 'reblock' ) {
+ if ( !isset( $params[4] ) ) {
+ // Very old log entry without duration: means infinite
+ $params[4] = 'infinite';
+ }
+ // 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[4] );
+ $params[4] = Message::rawParam( "<span class='blockExpiry' title='$durationTooltip'>" .
+ $this->context->getLanguage()->translateBlockExpiry( $params[4] ) . '</span>' );
+ $params[5] = isset( $params[5] ) ?
+ self::formatBlockFlags( $params[5], $this->context->getLanguage() ) : '';
+ }
+
+ return $params;
+ }
+
+ protected function extractParameters() {
+ $params = parent::extractParameters();
+ // Legacy log params returning the params in index 3 and 4, moved to 4 and 5
+ if ( $this->entry->isLegacy() && isset( $params[3] ) ) {
+ if ( isset( $params[4] ) ) {
+ $params[5] = $params[4];
+ }
+ $params[4] = $params[3];
+ $params[3] = '';
+ }
+ return $params;
+ }
+
+ public function getPreloadTitles() {
+ $title = $this->entry->getTarget();
+ // Preload user page for non-autoblocks
+ if ( substr( $title->getText(), 0, 1 ) !== '#' ) {
+ return array( $title->getTalkPage() );
+ }
+ return array();
+ }
+
+ public function getActionLinks() {
+ $subtype = $this->entry->getSubtype();
+ if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
+ || !( $subtype === 'block' || $subtype === 'reblock' )
+ || !$this->context->getUser()->isAllowed( 'block' )
+ ) {
+ return '';
+ }
+
+ // Show unblock/change block link
+ $title = $this->entry->getTarget();
+ $links = array(
+ Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Unblock', $title->getDBkey() ),
+ $this->msg( 'unblocklink' )->escaped()
+ ),
+ Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Block', $title->getDBkey() ),
+ $this->msg( 'change-blocklink' )->escaped()
+ )
+ );
+
+ return $this->msg( 'parentheses' )->rawParams(
+ $this->context->getLanguage()->pipeList( $links ) )->escaped();
+ }
+
+ /**
+ * Convert a comma-delimited list of block log flags
+ * into a more readable (and translated) form
+ *
+ * @param string $flags Flags to format
+ * @param Language $lang
+ * @return string
+ */
+ public static function formatBlockFlags( $flags, $lang ) {
+ $flags = trim( $flags );
+ if ( $flags === '' ) {
+ return ''; //nothing to do
+ }
+ $flags = explode( ',', $flags );
+ $flagsCount = count( $flags );
+
+ for ( $i = 0; $i < $flagsCount; $i++ ) {
+ $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
+ }
+
+ return wfMessage( 'parentheses' )->inLanguage( $lang )
+ ->rawParams( $lang->commaList( $flags ) )->escaped();
+ }
+
+ /**
+ * Translate a block log flag if possible
+ *
+ * @param int $flag Flag to translate
+ * @param Language $lang Language object to use
+ * @return string
+ */
+ public static function formatBlockFlag( $flag, $lang ) {
+ static $messages = array();
+
+ if ( !isset( $messages[$flag] ) ) {
+ $messages[$flag] = htmlspecialchars( $flag ); // Fallback
+
+ // For grepping. The following core messages can be used here:
+ // * block-log-flags-angry-autoblock
+ // * block-log-flags-anononly
+ // * block-log-flags-hiddenname
+ // * block-log-flags-noautoblock
+ // * block-log-flags-nocreate
+ // * block-log-flags-noemail
+ // * block-log-flags-nousertalk
+ $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang );
+
+ if ( $msg->exists() ) {
+ $messages[$flag] = $msg->escaped();
+ }
+ }
+
+ return $messages[$flag];
+ }
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ // While this looks wrong to be starting at 5 rather than 4, it's
+ // because getMessageParameters uses $4 for its own purposes.
+ '5::duration',
+ '6:array:flags',
+ '6::flags' => '6:array:flags',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ $subtype = $entry->getSubtype();
+ if ( $subtype === 'block' || $subtype === 'reblock' ) {
+ // Defaults for old log entries missing some fields
+ $params += array(
+ '5::duration' => 'infinite',
+ '6:array:flags' => array(),
+ );
+
+ if ( !is_array( $params['6:array:flags'] ) ) {
+ $params['6:array:flags'] = $params['6:array:flags'] === ''
+ ? array()
+ : explode( ',', $params['6:array:flags'] );
+ }
+
+ if ( !wfIsInfinity( $params['5::duration'] ) ) {
+ $ts = wfTimestamp( TS_UNIX, $entry->getTimestamp() );
+ $expiry = strtotime( $params['5::duration'], $ts );
+ if ( $expiry !== false && $expiry > 0 ) {
+ $params[':timestamp:expiry'] = $expiry;
+ }
+ }
+ }
+
+ return $params;
+ }
+
+ public function formatParametersForApi() {
+ $ret = parent::formatParametersForApi();
+ if ( isset( $ret['flags'] ) ) {
+ ApiResult::setIndexedTagName( $ret['flags'], 'f' );
+ }
+ return $ret;
+ }
+
+}
diff --git a/includes/logging/DeleteLogFormatter.php b/includes/logging/DeleteLogFormatter.php
index 8b30e9ba..f0598aa7 100644
--- a/includes/logging/DeleteLogFormatter.php
+++ b/includes/logging/DeleteLogFormatter.php
@@ -79,8 +79,10 @@ class DeleteLogFormatter extends LogFormatter {
$newParams = array_slice( $params, 0, 3 );
$newParams[3] = $changeText;
- $count = count( explode( ',', $params[$paramStart] ) );
- $newParams[4] = $this->context->getLanguage()->formatNum( $count );
+ $ids = is_array( $params[$paramStart] )
+ ? $params[$paramStart]
+ : explode( ',', $params[$paramStart] );
+ $newParams[4] = $this->context->getLanguage()->formatNum( count( $ids ) );
$this->parsedParametersDeleteLog = $newParams;
return $this->parsedParametersDeleteLog;
@@ -137,8 +139,10 @@ class DeleteLogFormatter extends LogFormatter {
// Different revision types use different URL params...
$key = $params[3];
- // This is a CSV of the IDs
- $ids = explode( ',', $params[4] );
+ // This is a array or CSV of the IDs
+ $ids = is_array( $params[4] )
+ ? $params[4]
+ : explode( ',', $params[4] );
$links = array();
@@ -192,6 +196,9 @@ class DeleteLogFormatter extends LogFormatter {
}
// This is a CSV of the IDs
$query = $params[3];
+ if ( is_array( $query ) ) {
+ $query = implode( ',', $query );
+ }
// Link to each hidden object ID, $params[1] is the url param
$revert = Linker::linkKnown(
SpecialPage::getTitleFor( 'Revisiondelete' ),
@@ -209,4 +216,67 @@ class DeleteLogFormatter extends LogFormatter {
return '';
}
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = array();
+
+ $subtype = $this->entry->getSubtype();
+ if ( in_array( $subtype, array( 'event', 'revision' ) ) ) {
+ $rawParams = $entry->getParameters();
+ if ( $subtype === 'event' ) {
+ array_unshift( $rawParams, 'logging' );
+ }
+
+ static $map = array(
+ '4::type',
+ '5::ids',
+ '6::ofield',
+ '7::nfield',
+ '4::ids' => '5::ids',
+ '5::ofield' => '6::ofield',
+ '6::nfield' => '7::nfield',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $rawParams[$index] ) ) {
+ $rawParams[$key] = $rawParams[$index];
+ unset( $rawParams[$index] );
+ }
+ }
+
+ $old = $this->parseBitField( $rawParams['6::ofield'] );
+ $new = $this->parseBitField( $rawParams['7::nfield'] );
+ if ( !is_array( $rawParams['5::ids'] ) ) {
+ $rawParams['5::ids'] = explode( ',', $rawParams['5::ids'] );
+ }
+
+ $params = array(
+ '::type' => $rawParams['4::type'],
+ ':array:ids' => $rawParams['5::ids'],
+ ':assoc:old' => array( 'bitmask' => $old ),
+ ':assoc:new' => array( 'bitmask' => $new ),
+ );
+
+ static $fields = array(
+ Revision::DELETED_TEXT => 'content',
+ Revision::DELETED_COMMENT => 'comment',
+ Revision::DELETED_USER => 'user',
+ Revision::DELETED_RESTRICTED => 'restricted',
+ );
+ foreach ( $fields as $bit => $key ) {
+ $params[':assoc:old'][$key] = (bool)( $old & $bit );
+ $params[':assoc:new'][$key] = (bool)( $new & $bit );
+ }
+ }
+
+ return $params;
+ }
+
+ public function formatParametersForApi() {
+ $ret = parent::formatParametersForApi();
+ if ( isset( $ret['ids'] ) ) {
+ ApiResult::setIndexedTagName( $ret['ids'], 'id' );
+ }
+ return $ret;
+ }
}
diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php
index 46c55157..66c2bde1 100644
--- a/includes/logging/LogEntry.php
+++ b/includes/logging/LogEntry.php
@@ -370,6 +370,9 @@ class ManualLogEntry extends LogEntryBase {
/** @var int ID of the log entry */
protected $id;
+ /** @var bool Whether this is a legacy log entry */
+ protected $legacy = false;
+
/**
* Constructor.
*
@@ -385,13 +388,14 @@ class ManualLogEntry extends LogEntryBase {
/**
* Set extra log parameters.
- * You can pass params to the log action message
- * by prefixing the keys with a number and colon.
- * The numbering should start with number 4, the
- * first three parameters are hardcoded for every
- * message. Example:
+ *
+ * You can pass params to the log action message by prefixing the keys with
+ * a number and optional type, using colons to separate the fields. The
+ * numbering should start with number 4, the first three parameters are
+ * hardcoded for every message. Example:
* $entry->setParameters(
- * '4:color' => 'blue',
+ * '4::color' => 'blue',
+ * '5:number:count' => 3000,
* 'animal' => 'dog'
* );
*
@@ -459,6 +463,16 @@ class ManualLogEntry extends LogEntryBase {
}
/**
+ * Set the 'legacy' flag
+ *
+ * @since 1.25
+ * @param bool $legacy
+ */
+ public function setLegacy( $legacy ) {
+ $this->legacy = $legacy;
+ }
+
+ /**
* TODO: document
*
* @since 1.19
@@ -533,10 +547,6 @@ 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;
}
@@ -640,6 +650,14 @@ class ManualLogEntry extends LogEntryBase {
return $this->comment;
}
+ /**
+ * @since 1.25
+ * @return bool
+ */
+ public function isLegacy() {
+ return $this->legacy;
+ }
+
public function getDeleted() {
return (int)$this->deleted;
}
diff --git a/includes/logging/LogEventsList.php b/includes/logging/LogEventsList.php
index 4dc25ef3..dfe31365 100644
--- a/includes/logging/LogEventsList.php
+++ b/includes/logging/LogEventsList.php
@@ -26,7 +26,7 @@
class LogEventsList extends ContextSource {
const NO_ACTION_LINK = 1;
const NO_EXTRA_USER_LINKS = 2;
- const USE_REVDEL_CHECKBOXES = 4;
+ const USE_CHECKBOXES = 4;
public $flags;
@@ -44,7 +44,7 @@ class LogEventsList extends ContextSource {
* 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.
+ * self::NO_EXTRA_USER_LINKS or self::USE_CHECKBOXES.
*/
public function __construct( $context, $unused = null, $flags = 0 ) {
if ( $context instanceof IContextSource ) {
@@ -58,17 +58,6 @@ class LogEventsList extends ContextSource {
}
/**
- * Deprecated alias for getTitle(); do not use.
- *
- * @deprecated since 1.20; use getTitle() instead.
- * @return Title
- */
- public function getDisplayTitle() {
- wfDeprecated( __METHOD__, '1.20' );
- return $this->getTitle();
- }
-
- /**
* Show options for the log list
*
* @param array|string $types
@@ -234,7 +223,8 @@ class LogEventsList extends ContextSource {
'user',
'mw-log-user',
15,
- $user
+ $user,
+ array( 'class' => 'mw-autocomplete-user' )
);
return '<span style="white-space: nowrap">' . $label . '</span>';
@@ -271,14 +261,21 @@ class LogEventsList extends ContextSource {
* @return string
*/
private function getExtraInputs( $types ) {
- $offender = $this->getRequest()->getVal( 'offender' );
- $user = User::newFromName( $offender, false );
- if ( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) {
- $offender = ''; // Blank field if invalid
- }
- if ( count( $types ) == 1 && $types[0] == 'suppress' ) {
- return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender',
- 'mw-log-offender', 20, $offender );
+ if ( count( $types ) == 1 ) {
+ if ( $types[0] == 'suppress' ) {
+ $offender = $this->getRequest()->getVal( 'offender' );
+ $user = User::newFromName( $offender, false );
+ if ( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) {
+ $offender = ''; // Blank field if invalid
+ }
+ return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender',
+ 'mw-log-offender', 20, $offender );
+ } else {
+ // Allow extensions to add their own extra inputs
+ $input = '';
+ Hooks::run( 'LogEventsListGetExtraInputs', array( $types[0], $this, &$input ) );
+ return $input;
+ }
}
return '';
@@ -344,14 +341,27 @@ class LogEventsList extends ContextSource {
*/
private function getShowHideLinks( $row ) {
// We don't want to see the links and
+ if ( $this->flags == self::NO_ACTION_LINK ) {
+ return '';
+ }
+
+ $user = $this->getUser();
+
+ // If change tag editing is available to this user, return the checkbox
+ if ( $this->flags & self::USE_CHECKBOXES && ChangeTags::showTagEditingUI( $user ) ) {
+ return Xml::check(
+ 'showhiderevisions',
+ false,
+ array( 'name' => 'ids[' . $row->log_id . ']' )
+ );
+ }
+
// no one can hide items from the suppress log.
- if ( ( $this->flags == self::NO_ACTION_LINK )
- || $row->log_type == 'suppress'
- ) {
+ if ( $row->log_type == 'suppress' ) {
return '';
}
+
$del = '';
- $user = $this->getUser();
// Don't show useless checkbox to people who cannot hide log entries
if ( $user->isAllowed( 'deletedhistory' ) ) {
$canHide = $user->isAllowed( 'deletelogentry' );
@@ -361,7 +371,7 @@ class LogEventsList extends ContextSource {
$canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed;
if ( $row->log_deleted || $canHide ) {
// Show checkboxes instead of links.
- if ( $canHide && $this->flags & self::USE_REVDEL_CHECKBOXES && !$canViewThisSuppressedEntry ) {
+ if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) {
// If event was hidden from sysops
if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) {
$del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
@@ -544,14 +554,16 @@ class LogEventsList extends ContextSource {
if ( $lim > 0 ) {
$pager->mLimit = $lim;
}
-
+ // Fetch the log rows and build the HTML if needed
$logBody = $pager->getBody();
+ $numRows = $pager->getNumRows();
+
$s = '';
if ( $logBody ) {
if ( $msgKey[0] ) {
$dir = $context->getLanguage()->getDir();
- $lang = $context->getLanguage()->getCode();
+ $lang = $context->getLanguage()->getHtmlCode();
$s = Xml::openElement( 'div', array(
'class' => "mw-warning-with-logexcerpt mw-content-$dir",
@@ -575,7 +587,7 @@ class LogEventsList extends ContextSource {
$context->msg( 'logempty' )->parse() );
}
- if ( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link
+ if ( $numRows > $pager->mLimit ) { # Show "Full log" link
$urlParam = array();
if ( $page instanceof Title ) {
$urlParam['page'] = $page->getPrefixedDBkey();
@@ -613,7 +625,7 @@ class LogEventsList extends ContextSource {
}
/* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
- if ( wfRunHooks( 'LogEventsListShowLogExtract', array( &$s, $types, $page, $user, $param ) ) ) {
+ if ( Hooks::run( 'LogEventsListShowLogExtract', array( &$s, $types, $page, $user, $param ) ) ) {
// $out can be either an OutputPage object or a String-by-reference
if ( $out instanceof OutputPage ) {
$out->addHTML( $s );
@@ -622,7 +634,7 @@ class LogEventsList extends ContextSource {
}
}
- return $pager->getNumRows();
+ return $numRows;
}
/**
diff --git a/includes/logging/LogFormatter.php b/includes/logging/LogFormatter.php
index 48a565f2..9c2fdd35 100644
--- a/includes/logging/LogFormatter.php
+++ b/includes/logging/LogFormatter.php
@@ -25,9 +25,12 @@
/**
* Implements the default log formatting.
- * Can be overridden by subclassing and setting
- * $wgLogActionsHandlers['type/subtype'] = 'class'; or
- * $wgLogActionsHandlers['type/*'] = 'class';
+ *
+ * Can be overridden by subclassing and setting:
+ *
+ * $wgLogActionsHandlers['type/subtype'] = 'class'; or
+ * $wgLogActionsHandlers['type/*'] = 'class';
+ *
* @since 1.19
*/
class LogFormatter {
@@ -80,6 +83,9 @@ class LogFormatter {
/** @var int Constant for handling log_deleted */
protected $audience = self::FOR_PUBLIC;
+ /** @var IContextSource Context for logging */
+ public $context;
+
/** @var bool Whether to output user tool links */
protected $linkFlood = false;
@@ -322,6 +328,57 @@ class LogFormatter {
break;
}
break;
+
+ case 'merge':
+ $text = wfMessage( 'pagemerge-logentry' )
+ ->rawParams( $target, $parameters['4::dest'], $parameters['5::mergepoint'] )
+ ->inContentLanguage()->escaped();
+ break;
+
+ case 'block':
+ switch ( $entry->getSubtype() ) {
+ case 'block':
+ global $wgContLang;
+ // Keep compatibility with extensions by checking for
+ // new key (5::duration/6::flags) or old key (0/optional 1)
+ if ( $entry->isLegacy() ) {
+ $rawDuration = $parameters[0];
+ $rawFlags = isset( $parameters[1] ) ? $parameters[1] : '';
+ } else {
+ $rawDuration = $parameters['5::duration'];
+ $rawFlags = $parameters['6::flags'];
+ }
+ $duration = $wgContLang->translateBlockExpiry( $rawDuration );
+ $flags = BlockLogFormatter::formatBlockFlags( $rawFlags, $wgContLang );
+ $text = wfMessage( 'blocklogentry' )
+ ->rawParams( $target, $duration, $flags )->inContentLanguage()->escaped();
+ break;
+ case 'unblock':
+ $text = wfMessage( 'unblocklogentry' )
+ ->rawParams( $target )->inContentLanguage()->escaped();
+ break;
+ case 'reblock':
+ global $wgContLang;
+ $duration = $wgContLang->translateBlockExpiry( $parameters['5::duration'] );
+ $flags = BlockLogFormatter::formatBlockFlags( $parameters['6::flags'], $wgContLang );
+ $text = wfMessage( 'reblock-logentry' )
+ ->rawParams( $target, $duration, $flags )->inContentLanguage()->escaped();
+ break;
+ }
+ break;
+
+ case 'import':
+ switch ( $entry->getSubtype() ) {
+ case 'upload':
+ $text = wfMessage( 'import-logentry-upload' )
+ ->rawParams( $target )->inContentLanguage()->escaped();
+ break;
+ case 'interwiki':
+ $text = wfMessage( 'import-logentry-interwiki' )
+ ->rawParams( $target )->inContentLanguage()->escaped();
+ break;
+ }
+ break;
// case 'suppress' --private log -- aaron (so we know who to blame in a few years :-D)
// default:
}
@@ -349,8 +406,10 @@ class LogFormatter {
$element = $this->styleRestricedElement( $element );
}
} else {
- $performer = $this->getPerformerElement() . $this->msg( 'word-separator' )->text();
- $element = $performer . $this->getRestrictedElement( 'rev-deleted-event' );
+ $sep = $this->msg( 'word-separator' );
+ $sep = $this->plaintext ? $sep->text() : $sep->escaped();
+ $performer = $this->getPerformerElement();
+ $element = $performer . $sep . $this->getRestrictedElement( 'rev-deleted-event' );
}
return $element;
@@ -413,7 +472,9 @@ class LogFormatter {
continue;
}
list( $index, $type, ) = explode( ':', $key, 3 );
- $params[$index - 1] = $this->formatParameterValue( $type, $value );
+ if ( ctype_digit( $index ) ) {
+ $params[$index - 1] = $this->formatParameterValue( $type, $value );
+ }
}
/* Message class doesn't like non consecutive numbering.
@@ -422,7 +483,8 @@ class LogFormatter {
*/
if ( count( $params ) ) {
$max = max( array_keys( $params ) );
- for ( $i = 4; $i < $max; $i++ ) {
+ // index 0 to 2 are added in getMessageParameters
+ for ( $i = 3; $i < $max; $i++ ) {
if ( !isset( $params[$i] ) ) {
$params[$i] = '';
}
@@ -480,8 +542,8 @@ class LogFormatter {
* * title-link: The value is a page title,
* returns link to this page
* * number: Format value as number
- * @param string $value The parameter value that should
- * be formated
+ * * list: Format value as a comma-separated list
+ * @param mixed $value The parameter value that should be formatted
* @return string|array Formated value
* @since 1.21
*/
@@ -492,6 +554,9 @@ class LogFormatter {
case 'raw':
$value = Message::rawParam( $value );
break;
+ case 'list':
+ $value = $this->context->getLanguage()->commaList( $value );
+ break;
case 'msg':
$value = $this->msg( $value )->text();
break;
@@ -629,7 +694,7 @@ class LogFormatter {
return $this->context->msg( $key );
}
- protected function makeUserLink( User $user ) {
+ protected function makeUserLink( User $user, $toolFlags = 0 ) {
if ( $this->plaintext ) {
$element = $user->getName();
} else {
@@ -639,9 +704,11 @@ class LogFormatter {
);
if ( $this->linkFlood ) {
- $element .= Linker::userToolLinksRedContribs(
+ $element .= Linker::userToolLinks(
$user->getId(),
$user->getName(),
+ true, // redContribsWhenNoEdits
+ $toolFlags,
$user->getEditCount()
);
}
@@ -666,6 +733,120 @@ class LogFormatter {
// problems with extensions
return $this->getMessageParameters();
}
+
+ /**
+ * Get the array of parameters, converted from legacy format if necessary.
+ * @since 1.25
+ * @return array
+ */
+ protected function getParametersForApi() {
+ return $this->entry->getParameters();
+ }
+
+ /**
+ * Format parameters for API output
+ *
+ * The result array should generally map named keys to values. Index and
+ * type should be omitted, e.g. "4::foo" should be returned as "foo" in the
+ * output. Values should generally be unformatted.
+ *
+ * Renames or removals of keys besides from the legacy numeric format to
+ * modern named style should be avoided. Any renames should be announced to
+ * the mediawiki-api-announce mailing list.
+ *
+ * @since 1.25
+ * @return array
+ */
+ public function formatParametersForApi() {
+ $logParams = array();
+ foreach ( $this->getParametersForApi() as $key => $value ) {
+ $vals = explode( ':', $key, 3 );
+ if ( count( $vals ) !== 3 ) {
+ $logParams[$key] = $value;
+ continue;
+ }
+ $logParams += $this->formatParameterValueForApi( $vals[2], $vals[1], $value );
+ }
+ ApiResult::setIndexedTagName( $logParams, 'param' );
+ ApiResult::setArrayType( $logParams, 'assoc' );
+
+ return $logParams;
+ }
+
+ /**
+ * Format a single parameter value for API output
+ *
+ * @since 1.25
+ * @param string $name
+ * @param string $type
+ * @param string $value
+ * @return array
+ */
+ protected function formatParameterValueForApi( $name, $type, $value ) {
+ $type = strtolower( trim( $type ) );
+ switch ( $type ) {
+ case 'bool':
+ $value = (bool)$value;
+ break;
+
+ case 'number':
+ if ( ctype_digit( $value ) ) {
+ $value = (int)$value;
+ } else {
+ $value = (float)$value;
+ }
+ break;
+
+ case 'array':
+ case 'assoc':
+ case 'kvp':
+ if ( is_array( $value ) ) {
+ ApiResult::setArrayType( $value, $type );
+ }
+ break;
+
+ case 'timestamp':
+ $value = wfTimestamp( TS_ISO_8601, $value );
+ break;
+
+ case 'msg':
+ case 'msg-content':
+ $msg = $this->msg( $value );
+ if ( $type === 'msg-content' ) {
+ $msg->inContentLanguage();
+ }
+ $value = array();
+ $value["{$name}_key"] = $msg->getKey();
+ if ( $msg->getParams() ) {
+ $value["{$name}_params"] = $msg->getParams();
+ }
+ $value["{$name}_text"] = $msg->text();
+ return $value;
+
+ case 'title':
+ case 'title-link':
+ $title = Title::newFromText( $value );
+ if ( $title ) {
+ $value = array();
+ ApiQueryBase::addTitleInfo( $value, $title, "{$name}_" );
+ }
+ return $value;
+
+ case 'user':
+ case 'user-link':
+ $user = User::newFromName( $value );
+ if ( $user ) {
+ $value = $user->getName();
+ }
+ break;
+
+ default:
+ // do nothing
+ break;
+ }
+
+ return array( $name => $value );
+ }
}
/**
@@ -725,7 +906,9 @@ class LegacyLogFormatter extends LogFormatter {
$performer = $this->getPerformerElement();
if ( !$this->irctext ) {
- $action = $performer . $this->msg( 'word-separator' )->text() . $action;
+ $sep = $this->msg( 'word-separator' );
+ $sep = $this->plaintext ? $sep->text() : $sep->escaped();
+ $action = $performer . $sep . $action;
}
return $action;
@@ -745,29 +928,7 @@ class LegacyLogFormatter extends LogFormatter {
$type = $this->entry->getType();
$subtype = $this->entry->getSubtype();
- // Show unblock/change block link
- if ( ( $type == 'block' || $type == 'suppress' )
- && ( $subtype == 'block' || $subtype == 'reblock' )
- ) {
- if ( !$this->context->getUser()->isAllowed( 'block' ) ) {
- return '';
- }
-
- $links = array(
- Linker::linkKnown(
- SpecialPage::getTitleFor( 'Unblock', $title->getDBkey() ),
- $this->msg( 'unblocklink' )->escaped()
- ),
- Linker::linkKnown(
- SpecialPage::getTitleFor( 'Block', $title->getDBkey() ),
- $this->msg( 'change-blocklink' )->escaped()
- )
- );
-
- return $this->msg( 'parentheses' )->rawParams(
- $this->context->getLanguage()->pipeList( $links ) )->escaped();
- // Show change protection link
- } elseif ( $type == 'protect'
+ if ( $type == 'protect'
&& ( $subtype == 'protect' || $subtype == 'modify' || $subtype == 'unprotect' )
) {
$links = array(
@@ -791,26 +952,6 @@ class LegacyLogFormatter extends LogFormatter {
return $this->msg( 'parentheses' )->rawParams(
$this->context->getLanguage()->pipeList( $links ) )->escaped();
- // Show unmerge link
- } elseif ( $type == 'merge' && $subtype == 'merge' ) {
- if ( !$this->context->getUser()->isAllowed( 'mergehistory' ) ) {
- return '';
- }
-
- $params = $this->extractParameters();
- $revert = Linker::linkKnown(
- SpecialPage::getTitleFor( 'MergeHistory' ),
- $this->msg( 'revertmerge' )->escaped(),
- array(),
- array(
- 'target' => $params[3],
- 'dest' => $title->getPrefixedDBkey(),
- 'mergepoint' => $params[4],
- 'submitted' => 1 // show the revisions immediately
- )
- );
-
- return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
}
// Do nothing. The implementation is handled by the hook modifiying the
@@ -826,7 +967,7 @@ class LegacyLogFormatter extends LogFormatter {
$params = $this->entry->getParameters();
- wfRunHooks( 'LogLine', array( $type, $subtype, $title, $params,
+ Hooks::run( 'LogLine', array( $type, $subtype, $title, $params,
&$this->comment, &$this->revert, $this->entry->getTimestamp() ) );
return $this->revert;
diff --git a/includes/logging/LogPage.php b/includes/logging/LogPage.php
index ce5b972f..82e5808a 100644
--- a/includes/logging/LogPage.php
+++ b/includes/logging/LogPage.php
@@ -288,24 +288,8 @@ class LogPage {
$details = '';
array_unshift( $params, $titleLink );
- // User suppression
- if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
- if ( $skin ) {
- // 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] );
- }
-
- $params[2] = isset( $params[2] ) ?
- self::formatBlockFlags( $params[2], $langObj ) : '';
// Page protections
- } elseif ( $type == 'protect' && count( $params ) == 3 ) {
+ if ( $type == 'protect' && count( $params ) == 3 ) {
// Restrictions and expiries
if ( $skin ) {
$details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" );
@@ -370,69 +354,22 @@ class LogPage {
return $title->getPrefixedText();
}
- switch ( $type ) {
- case 'move':
- $titleLink = Linker::link(
- $title,
- htmlspecialchars( $title->getPrefixedText() ),
- array(),
- array( 'redirect' => 'no' )
- );
-
- $targetTitle = Title::newFromText( $params[0] );
-
- if ( !$targetTitle ) {
- # Workaround for broken database
- $params[0] = htmlspecialchars( $params[0] );
- } else {
- $params[0] = Linker::link(
- $targetTitle,
- htmlspecialchars( $params[0] )
- );
- }
- break;
- case 'block':
- if ( substr( $title->getText(), 0, 1 ) == '#' ) {
- $titleLink = $title->getText();
- } else {
- // @todo Store the user identifier in the parameters
- // to make this faster for future log entries
- $id = User::idFromName( $title->getText() );
- $titleLink = Linker::userLink( $id, $title->getText() )
- . Linker::userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK );
- }
- break;
- case 'merge':
- $titleLink = Linker::link(
- $title,
- $title->getPrefixedText(),
- array(),
- array( 'redirect' => 'no' )
- );
- $params[0] = Linker::link(
- Title::newFromText( $params[0] ),
- htmlspecialchars( $params[0] )
- );
- $params[1] = $lang->timeanddate( $params[1] );
- break;
- default:
- if ( $title->isSpecialPage() ) {
- list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
-
- # Use the language name for log titles, rather than Log/X
- if ( $name == 'Log' ) {
- $logPage = new LogPage( $par );
- $titleLink = Linker::link( $title, $logPage->getName()->escaped() );
- $titleLink = wfMessage( 'parentheses' )
- ->inLanguage( $lang )
- ->rawParams( $titleLink )
- ->escaped();
- } else {
- $titleLink = Linker::link( $title );
- }
- } else {
- $titleLink = Linker::link( $title );
- }
+ if ( $title->isSpecialPage() ) {
+ list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
+
+ # Use the language name for log titles, rather than Log/X
+ if ( $name == 'Log' ) {
+ $logPage = new LogPage( $par );
+ $titleLink = Linker::link( $title, $logPage->getName()->escaped() );
+ $titleLink = wfMessage( 'parentheses' )
+ ->inLanguage( $lang )
+ ->rawParams( $titleLink )
+ ->escaped();
+ } else {
+ $titleLink = Linker::link( $title );
+ }
+ } else {
+ $titleLink = Linker::link( $title );
}
return $titleLink;
@@ -485,6 +422,10 @@ class LogPage {
$logEntry->setTarget( $target );
$logEntry->setPerformer( $doer );
$logEntry->setParameters( $params );
+ // All log entries using the LogPage to insert into the logging table
+ // are using the old logging system and therefore the legacy flag is
+ // needed to say the LogFormatter the parameters have numeric keys
+ $logEntry->setLegacy( true );
$formatter = LogFormatter::newFromEntry( $logEntry );
$context = RequestContext::newExtraneousContext( $target );
@@ -550,61 +491,6 @@ class LogPage {
}
/**
- * Convert a comma-delimited list of block log flags
- * into a more readable (and translated) form
- *
- * @param string $flags Flags to format
- * @param Language $lang
- * @return string
- */
- public static function formatBlockFlags( $flags, $lang ) {
- $flags = trim( $flags );
- if ( $flags === '' ) {
- return ''; //nothing to do
- }
- $flags = explode( ',', $flags );
- $flagsCount = count( $flags );
-
- for ( $i = 0; $i < $flagsCount; $i++ ) {
- $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
- }
-
- return wfMessage( 'parentheses' )->inLanguage( $lang )
- ->rawParams( $lang->commaList( $flags ) )->escaped();
- }
-
- /**
- * Translate a block log flag if possible
- *
- * @param int $flag Flag to translate
- * @param Language $lang Language object to use
- * @return string
- */
- public static function formatBlockFlag( $flag, $lang ) {
- static $messages = array();
-
- if ( !isset( $messages[$flag] ) ) {
- $messages[$flag] = htmlspecialchars( $flag ); // Fallback
-
- // For grepping. The following core messages can be used here:
- // * block-log-flags-angry-autoblock
- // * block-log-flags-anononly
- // * block-log-flags-hiddenname
- // * block-log-flags-noautoblock
- // * block-log-flags-nocreate
- // * block-log-flags-noemail
- // * block-log-flags-nousertalk
- $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang );
-
- if ( $msg->exists() ) {
- $messages[$flag] = $msg->escaped();
- }
- }
-
- return $messages[$flag];
- }
-
- /**
* Name of the log.
* @return Message
* @since 1.19
diff --git a/includes/logging/LogPager.php b/includes/logging/LogPager.php
index 256934e4..bf489ab9 100644
--- a/includes/logging/LogPager.php
+++ b/includes/logging/LogPager.php
@@ -323,7 +323,6 @@ class LogPager extends ReverseChronologicalPager {
}
public function getStartBody() {
- wfProfileIn( __METHOD__ );
# Do a link batch query
if ( $this->getNumRows() > 0 ) {
$lb = new LinkBatch;
@@ -339,7 +338,6 @@ class LogPager extends ReverseChronologicalPager {
$lb->execute();
$this->mResult->seek( 0 );
}
- wfProfileOut( __METHOD__ );
return '';
}
diff --git a/includes/logging/MergeLogFormatter.php b/includes/logging/MergeLogFormatter.php
new file mode 100644
index 00000000..36e383b0
--- /dev/null
+++ b/includes/logging/MergeLogFormatter.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Formatter for merge 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
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.25
+ */
+
+/**
+ * This class formats merge log entries.
+ *
+ * @since 1.25
+ */
+class MergeLogFormatter extends LogFormatter {
+ public function getPreloadTitles() {
+ $params = $this->extractParameters();
+
+ return array( Title::newFromText( $params[3] ) );
+ }
+
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+ $oldname = $this->makePageLink( $this->entry->getTarget(), array( 'redirect' => 'no' ) );
+ $newname = $this->makePageLink( Title::newFromText( $params[3] ) );
+ $params[2] = Message::rawParam( $oldname );
+ $params[3] = Message::rawParam( $newname );
+ $params[4] = $this->context->getLanguage()
+ ->userTimeAndDate( $params[4], $this->context->getUser() );
+ return $params;
+ }
+
+ public function getActionLinks() {
+ if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
+ || !$this->context->getUser()->isAllowed( 'mergehistory' )
+ ) {
+ return '';
+ }
+
+ // Show unmerge link
+ $params = $this->extractParameters();
+ $revert = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'MergeHistory' ),
+ $this->msg( 'revertmerge' )->escaped(),
+ array(),
+ array(
+ 'target' => $params[3],
+ 'dest' => $this->entry->getTarget()->getPrefixedDBkey(),
+ 'mergepoint' => $params[4],
+ 'submitted' => 1 // show the revisions immediately
+ )
+ );
+
+ return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
+ }
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4:title:dest',
+ '5:timestamp:mergepoint',
+ '4::dest' => '4:title:dest',
+ '5::mergepoint' => '5:timestamp:mergepoint',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
+}
diff --git a/includes/logging/MoveLogFormatter.php b/includes/logging/MoveLogFormatter.php
index 39130163..7f5e9efa 100644
--- a/includes/logging/MoveLogFormatter.php
+++ b/includes/logging/MoveLogFormatter.php
@@ -37,8 +37,9 @@ class MoveLogFormatter extends LogFormatter {
protected function getMessageKey() {
$key = parent::getMessageKey();
- $params = $this->getMessageParameters();
+ $params = $this->extractParameters();
if ( isset( $params[4] ) && $params[4] === '1' ) {
+ // Messages: logentry-move-move-noredirect, logentry-move-move_redir-noredirect
$key .= '-noredirect';
}
@@ -51,6 +52,7 @@ class MoveLogFormatter extends LogFormatter {
$newname = $this->makePageLink( Title::newFromText( $params[3] ) );
$params[2] = Message::rawParam( $oldname );
$params[3] = Message::rawParam( $newname );
+ unset( $params[4] ); // handled in getMessageKey
return $params;
}
@@ -83,4 +85,29 @@ class MoveLogFormatter extends LogFormatter {
return $this->msg( 'parentheses' )->rawParams( $revert )->escaped();
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4:title:target',
+ '5:bool:suppressredirect',
+ '4::target' => '4:title:target',
+ '5::noredir' => '5:bool:suppressredirect',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ if ( !isset( $params['5:bool:suppressredirect'] ) ) {
+ $params['5:bool:suppressredirect'] = false;
+ }
+
+ return $params;
+ }
+
}
diff --git a/includes/logging/PatrolLogFormatter.php b/includes/logging/PatrolLogFormatter.php
index 2abaf173..00337432 100644
--- a/includes/logging/PatrolLogFormatter.php
+++ b/includes/logging/PatrolLogFormatter.php
@@ -62,4 +62,24 @@ class PatrolLogFormatter extends LogFormatter {
return $params;
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4::curid',
+ '5::previd',
+ '6:bool:auto',
+ '6::auto' => '6:bool:auto',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
}
diff --git a/includes/logging/RightsLogFormatter.php b/includes/logging/RightsLogFormatter.php
index ac252aeb..352bda58 100644
--- a/includes/logging/RightsLogFormatter.php
+++ b/includes/logging/RightsLogFormatter.php
@@ -67,20 +67,8 @@ class RightsLogFormatter extends LogFormatter {
return $params;
}
- $oldGroups = $params[3];
- $newGroups = $params[4];
-
- // Less old entries
- if ( $oldGroups === '' ) {
- $oldGroups = array();
- } elseif ( is_string( $oldGroups ) ) {
- $oldGroups = array_map( 'trim', explode( ',', $oldGroups ) );
- }
- if ( $newGroups === '' ) {
- $newGroups = array();
- } elseif ( is_string( $newGroups ) ) {
- $newGroups = array_map( 'trim', explode( ',', $newGroups ) );
- }
+ $oldGroups = $this->makeGroupArray( $params[3] );
+ $newGroups = $this->makeGroupArray( $params[4] );
$userName = $this->entry->getTarget()->getText();
if ( !$this->plaintext && count( $oldGroups ) ) {
@@ -110,4 +98,53 @@ class RightsLogFormatter extends LogFormatter {
return $params;
}
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ '4:array:oldgroups',
+ '5:array:newgroups',
+ '4::oldgroups' => '4:array:oldgroups',
+ '5::newgroups' => '5:array:newgroups',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ // Really old entries does not have log params
+ if ( isset( $params['4:array:oldgroups'] ) ) {
+ $params['4:array:oldgroups'] = $this->makeGroupArray( $params['4:array:oldgroups'] );
+ }
+ if ( isset( $params['5:array:newgroups'] ) ) {
+ $params['5:array:newgroups'] = $this->makeGroupArray( $params['5:array:newgroups'] );
+ }
+
+ return $params;
+ }
+
+ public function formatParametersForApi() {
+ $ret = parent::formatParametersForApi();
+ if ( isset( $ret['oldgroups'] ) ) {
+ ApiResult::setIndexedTagName( $ret['oldgroups'], 'g' );
+ }
+ if ( isset( $ret['newgroups'] ) ) {
+ ApiResult::setIndexedTagName( $ret['newgroups'], 'g' );
+ }
+ return $ret;
+ }
+
+ private function makeGroupArray( $group ) {
+ // Migrate old group params from string to array
+ if ( $group === '' ) {
+ $group = array();
+ } elseif ( is_string( $group ) ) {
+ $group = array_map( 'trim', explode( ',', $group ) );
+ }
+ return $group;
+ }
}
diff --git a/includes/logging/TagLogFormatter.php b/includes/logging/TagLogFormatter.php
new file mode 100644
index 00000000..5a58c331
--- /dev/null
+++ b/includes/logging/TagLogFormatter.php
@@ -0,0 +1,49 @@
+<?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
+ */
+
+/**
+ * This class formats tag log entries.
+ *
+ * Parameters (one-based indexes):
+ * 4::revid
+ * 5::logid
+ * 6:list:tagsAdded
+ * 7:number:tagsAddedCount
+ * 8:list:tagsRemoved
+ * 9:number:tagsRemovedCount
+ *
+ * @since 1.25
+ */
+class TagLogFormatter extends LogFormatter {
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ $params = $this->getMessageParameters();
+
+ $add = ( isset( $params[6] ) && isset( $params[6]['num'] ) && $params[6]['num'] );
+ $remove = ( isset( $params[8] ) && isset( $params[8]['num'] ) && $params[8]['num'] );
+ $key .= ( $remove ? ( $add ? '' : '-remove' ) : '-add' );
+
+ if ( isset( $params[4] ) && $params[4] ) {
+ $key .= '-logentry';
+ } else {
+ $key .= '-revision';
+ }
+
+ return $key;
+ }
+}
diff --git a/includes/logging/UploadLogFormatter.php b/includes/logging/UploadLogFormatter.php
new file mode 100644
index 00000000..52961dc4
--- /dev/null
+++ b/includes/logging/UploadLogFormatter.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Formatter for upload 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
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.25
+ */
+
+/**
+ * This class formats upload log entries.
+ *
+ * @since 1.25
+ */
+class UploadLogFormatter extends LogFormatter {
+
+ protected function getParametersForApi() {
+ $entry = $this->entry;
+ $params = $entry->getParameters();
+
+ static $map = array(
+ 'img_timestamp' => ':timestamp:img_timestamp',
+ );
+ foreach ( $map as $index => $key ) {
+ if ( isset( $params[$index] ) ) {
+ $params[$key] = $params[$index];
+ unset( $params[$index] );
+ }
+ }
+
+ return $params;
+ }
+
+}
diff --git a/includes/mail/EmailNotification.php b/includes/mail/EmailNotification.php
index 8215403e..81c4e38d 100644
--- a/includes/mail/EmailNotification.php
+++ b/includes/mail/EmailNotification.php
@@ -205,8 +205,6 @@ class EmailNotification {
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
@@ -224,9 +222,8 @@ class EmailNotification {
$formattedPageStatus = array( 'deleted', 'created', 'moved', 'restored', 'changed' );
- wfRunHooks( 'UpdateUserMailerFormattedPageStatus', array( &$formattedPageStatus ) );
+ Hooks::run( 'UpdateUserMailerFormattedPageStatus', array( &$formattedPageStatus ) );
if ( !in_array( $this->pageStatus, $formattedPageStatus ) ) {
- wfProfileOut( __METHOD__ );
throw new MWException( 'Not a valid page status!' );
}
@@ -251,7 +248,7 @@ class EmailNotification {
&& $watchingUser->isEmailConfirmed()
&& $watchingUser->getID() != $userTalkId
) {
- if ( wfRunHooks( 'SendWatchlistEmailNotification', array( $watchingUser, $title, $this ) ) ) {
+ if ( Hooks::run( 'SendWatchlistEmailNotification', array( $watchingUser, $title, $this ) ) ) {
$this->compose( $watchingUser );
}
}
@@ -270,7 +267,6 @@ class EmailNotification {
}
$this->sendMails();
- wfProfileOut( __METHOD__ );
}
/**
@@ -295,7 +291,7 @@ class EmailNotification {
) {
if ( !$targetUser->isEmailConfirmed() ) {
wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
- } elseif ( !wfRunHooks( 'AbortTalkPageEmailNotification', array( $targetUser, $title ) ) ) {
+ } elseif ( !Hooks::run( '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" );
diff --git a/includes/mail/MailAddress.php b/includes/mail/MailAddress.php
index 68179088..7a228bda 100644
--- a/includes/mail/MailAddress.php
+++ b/includes/mail/MailAddress.php
@@ -38,7 +38,7 @@ class MailAddress {
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' );
+ wfDeprecated( __METHOD__ . ' with a User object', '1.24' );
$this->address = $address->getEmail();
$this->name = $address->getName();
$this->realName = $address->getRealName();
diff --git a/includes/mail/UserMailer.php b/includes/mail/UserMailer.php
index b5a57a84..3cabdaeb 100644
--- a/includes/mail/UserMailer.php
+++ b/includes/mail/UserMailer.php
@@ -191,7 +191,7 @@ class UserMailer {
$extraParams = $wgAdditionalMailParams;
// Hook to generate custom VERP address for 'Return-Path'
- wfRunHooks( 'UserMailerChangeReturnPath', array( $to, &$returnPath ) );
+ Hooks::run( '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.
@@ -250,7 +250,7 @@ class UserMailer {
$headers['Content-transfer-encoding'] = '8bit';
}
- $ret = wfRunHooks( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) );
+ $ret = Hooks::run( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) );
if ( $ret === false ) {
// the hook implementation will return false to skip regular mail sending
return Status::newGood();
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
index d8b0ba64..52f9518f 100644
--- a/includes/media/BMP.php
+++ b/includes/media/BMP.php
@@ -71,7 +71,7 @@ class BmpHandler extends BitmapHandler {
try {
$w = wfUnpack( 'V', $w, 4 );
$h = wfUnpack( 'V', $h, 4 );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
return false;
}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index e81b37de..eadcf94b 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -142,7 +142,7 @@ class BitmapHandler extends TransformationalImageHandler {
$env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
}
- $rotation = $this->getRotation( $image );
+ $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
$cmd = call_user_func_array( 'wfEscapeShellArg', array_merge(
@@ -169,10 +169,8 @@ class BitmapHandler extends TransformationalImageHandler {
array( $this->escapeMagickOutput( $params['dstPath'] ) ) ) );
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
- wfProfileIn( 'convert' );
$retval = 0;
$err = wfShellExecWithStderr( $cmd, $retval, $env );
- wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
@@ -204,7 +202,7 @@ class BitmapHandler extends TransformationalImageHandler {
/ ( $params['srcWidth'] + $params['srcHeight'] )
< $wgSharpenReductionThreshold
) {
- // Hack, since $wgSharpenParamater is written specifically for the command line convert
+ // Hack, since $wgSharpenParameter is written specifically for the command line convert
list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
$im->sharpenImage( $radius, $sigma );
}
@@ -223,7 +221,7 @@ class BitmapHandler extends TransformationalImageHandler {
}
}
- $rotation = $this->getRotation( $image );
+ $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image );
list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
$im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
@@ -280,10 +278,8 @@ class BitmapHandler extends TransformationalImageHandler {
$cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ),
str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size
wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
- wfProfileIn( 'convert' );
$retval = 0;
$err = wfShellExecWithStderr( $cmd, $retval );
- wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
@@ -344,7 +340,7 @@ class BitmapHandler extends TransformationalImageHandler {
$src_image = call_user_func( $loader, $params['srcPath'] );
- $rotation = function_exists( 'imagerotate' ) ? $this->getRotation( $image ) : 0;
+ $rotation = function_exists( 'imagerotate' ) && !isset( $params['disableRotation'] ) ? $this->getRotation( $image ) : 0;
list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
$dst_image = imagecreatetruecolor( $width, $height );
@@ -457,10 +453,8 @@ class BitmapHandler extends TransformationalImageHandler {
" -rotate " . wfEscapeShellArg( "-$rotation" ) . " " .
wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
- wfProfileIn( 'convert' );
$retval = 0;
$err = wfShellExecWithStderr( $cmd, $retval );
- wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
index 1d790155..c8d37bbb 100644
--- a/includes/media/BitmapMetadataHandler.php
+++ b/includes/media/BitmapMetadataHandler.php
@@ -61,7 +61,7 @@ class BitmapMetadataHandler {
private function doApp13( $app13 ) {
try {
$this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// Error reading the iptc hash information.
// This probably means the App13 segment is something other than what we expect.
// However, still try to read it, and treat it as if the hash didn't exist.
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index daeb475f..1b0eb492 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -221,11 +221,9 @@ class DjVuHandler extends ImageHandler {
$cmd .= " | {$wgDjvuPostProcessor}";
}
$cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1';
- wfProfileIn( 'ddjvu' );
wfDebug( __METHOD__ . ": $cmd\n" );
$retval = '';
$err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'ddjvu' );
$removed = $this->removeBadFile( $dstPath, $retval );
if ( $retval != 0 || $removed ) {
@@ -266,6 +264,7 @@ class DjVuHandler extends ImageHandler {
*
* @param File $file The DjVu file in question
* @return string XML metadata as a string.
+ * @throws MWException
*/
private function getUnserializedMetadata( File $file ) {
$metadata = $file->getMetadata();
@@ -312,7 +311,6 @@ class DjVuHandler extends ImageHandler {
return false;
}
- wfProfileIn( __METHOD__ );
wfSuppressWarnings();
try {
@@ -338,7 +336,6 @@ class DjVuHandler extends ImageHandler {
wfDebug( "Bogus multipage XML metadata on '{$image->getName()}'\n" );
}
wfRestoreWarnings();
- wfProfileOut( __METHOD__ );
if ( $gettext ) {
return $image->djvuTextTree;
} else {
diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php
index 6ff19c90..e8faa70a 100644
--- a/includes/media/DjVuImage.php
+++ b/includes/media/DjVuImage.php
@@ -265,37 +265,34 @@ class DjVuImage {
/**
* Return an XML string describing the DjVu image
- * @return string
+ * @return string|bool
*/
function retrieveMetaData() {
global $wgDjvuToXML, $wgDjvuDump, $wgDjvuTxt;
- wfProfileIn( __METHOD__ );
+
+ if ( !$this->isValid() ) {
+ return false;
+ }
if ( isset( $wgDjvuDump ) ) {
# djvudump is faster as of version 3.5
# http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
- wfProfileIn( 'djvudump' );
$cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename );
$dump = wfShellExec( $cmd );
$xml = $this->convertDumpToXML( $dump );
- wfProfileOut( 'djvudump' );
} elseif ( isset( $wgDjvuToXML ) ) {
- wfProfileIn( 'djvutoxml' );
$cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' .
wfEscapeShellArg( $this->mFilename );
$xml = wfShellExec( $cmd );
- wfProfileOut( 'djvutoxml' );
} else {
$xml = null;
}
# Text layer
if ( isset( $wgDjvuTxt ) ) {
- wfProfileIn( 'djvutxt' );
$cmd = wfEscapeShellArg( $wgDjvuTxt ) . ' --detail=page ' . wfEscapeShellArg( $this->mFilename );
wfDebug( __METHOD__ . ": $cmd\n" );
$retval = '';
$txt = wfShellExec( $cmd, $retval, array(), array( 'memory' => self::DJVUTXT_MEMORY_LIMIT ) );
- wfProfileOut( 'djvutxt' );
if ( $retval == 0 ) {
# Strip some control characters
$txt = preg_replace( "/[\013\035\037]/", "", $txt );
@@ -316,14 +313,13 @@ EOR;
$xml = $xml . $txt . '</mw-djvu>';
}
}
- wfProfileOut( __METHOD__ );
return $xml;
}
function pageTextCallback( $matches ) {
# Get rid of invalid UTF-8, strip control characters
- $val = htmlspecialchars( UtfNormal::cleanUp( stripcslashes( $matches[1] ) ) );
+ $val = htmlspecialchars( UtfNormal\Validator::cleanUp( stripcslashes( $matches[1] ) ) );
$val = str_replace( array( "\n", '�' ), array( '&#10;', '' ), $val );
return '<PAGE value="' . $val . '" />';
}
diff --git a/includes/media/Exif.php b/includes/media/Exif.php
index 018b58c5..33868689 100644
--- a/includes/media/Exif.php
+++ b/includes/media/Exif.php
@@ -477,7 +477,7 @@ class Exif {
} else {
// if valid utf-8, assume that, otherwise assume windows-1252
$valCopy = $val;
- UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy.
+ UtfNormal\Validator::quickIsNFCVerify( $valCopy ); //validates $valCopy.
if ( $valCopy !== $val ) {
wfSuppressWarnings();
$val = iconv( 'Windows-1252', 'UTF-8//IGNORE', $val );
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
index b7657cb3..f56a947f 100644
--- a/includes/media/ExifBitmap.php
+++ b/includes/media/ExifBitmap.php
@@ -125,15 +125,16 @@ class ExifBitmapHandler extends BitmapHandler {
/**
* @param File $image
+ * @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image ) {
+ function formatMetadata( $image, $context = false ) {
$meta = $this->getCommonMetaArray( $image );
if ( count( $meta ) === 0 ) {
return false;
}
- return $this->formatMetadataHelper( $meta );
+ return $this->formatMetadataHelper( $meta, $context );
}
public function getCommonMetaArray( File $file ) {
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
index 43569539..501bb9c2 100644
--- a/includes/media/FormatMetadata.php
+++ b/includes/media/FormatMetadata.php
@@ -1595,11 +1595,8 @@ class FormatMetadata extends ContextSource {
public function fetchExtendedMetadata( File $file ) {
global $wgMemc;
- wfProfileIn( __METHOD__ );
-
// If revision deleted, exit immediately
if ( $file->isDeleted( File::DELETED_FILE ) ) {
- wfProfileOut( __METHOD__ );
return array();
}
@@ -1614,7 +1611,7 @@ class FormatMetadata extends ContextSource {
$cachedValue = $wgMemc->get( $cacheKey );
if (
$cachedValue
- && wfRunHooks( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) )
+ && Hooks::run( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) )
) {
$extendedMetadata = $cachedValue['data'];
} else {
@@ -1624,17 +1621,16 @@ class FormatMetadata extends ContextSource {
if ( $this->singleLang ) {
$this->resolveMultilangMetadata( $extendedMetadata );
}
+ $this->discardMultipleValues( $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 );
+ $this->sanitizeArrayForAPI( $extendedMetadata );
$valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() );
$wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime );
}
- wfProfileOut( __METHOD__ );
-
return $extendedMetadata;
}
@@ -1656,8 +1652,6 @@ class FormatMetadata extends ContextSource {
return $file->getExtendedMetadata() ?: array();
}
- wfProfileIn( __METHOD__ );
-
$uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
$fileMetadata = array(
@@ -1685,19 +1679,6 @@ class FormatMetadata extends ContextSource {
);
}
- $common = $file->getCommonMetaArray();
-
- if ( $common !== false ) {
- foreach ( $common as $key => $value ) {
- $fileMetadata[$key] = array(
- 'value' => $value,
- 'source' => 'file-metadata',
- );
- }
- }
-
- wfProfileOut( __METHOD__ );
-
return $fileMetadata;
}
@@ -1714,9 +1695,8 @@ class FormatMetadata extends ContextSource {
protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata,
&$maxCacheTime
) {
- wfProfileIn( __METHOD__ );
- wfRunHooks( 'GetExtendedMetadata', array(
+ Hooks::run( 'GetExtendedMetadata', array(
&$extendedMetadata,
$file,
$this->getContext(),
@@ -1731,8 +1711,6 @@ class FormatMetadata extends ContextSource {
}
}
- wfProfileOut( __METHOD__ );
-
return $extendedMetadata;
}
@@ -1777,6 +1755,32 @@ class FormatMetadata extends ContextSource {
}
/**
+ * Turns an XMP-style multivalue array into a single value by dropping all but the first value.
+ * If the value is not a multivalue array (or a multivalue array inside a multilang array), it is returned unchanged.
+ * See mediawiki.org/wiki/Manual:File_metadata_handling#Multi-language_array_format
+ * @param mixed $value
+ * @return mixed The value, or the first value if there were multiple ones
+ * @since 1.25
+ */
+ protected function resolveMultivalueValue( $value ) {
+ if ( !is_array( $value ) ) {
+ return $value;
+ } elseif ( isset( $value['_type'] ) && $value['_type'] === 'lang' ) { // if this is a multilang array, process fields separately
+ $newValue = array();
+ foreach ( $value as $k => $v ) {
+ $newValue[$k] = $this->resolveMultivalueValue( $v );
+ }
+ return $newValue;
+ } else { // _type is 'ul' or 'ol' or missing in which case it defaults to 'ul'
+ list( $k, $v ) = each( $value );
+ if ( $k === '_type' ) {
+ $v = current( $value );
+ }
+ return $v;
+ }
+ }
+
+ /**
* Takes an array returned by the getExtendedMetadata* functions,
* and resolves multi-language values in it.
* @param array $metadata
@@ -1794,18 +1798,40 @@ class FormatMetadata extends ContextSource {
}
/**
+ * Takes an array returned by the getExtendedMetadata* functions,
+ * and turns all fields into single-valued ones by dropping extra values.
+ * @param array $metadata
+ * @since 1.25
+ */
+ protected function discardMultipleValues( &$metadata ) {
+ if ( !is_array( $metadata ) ) {
+ return;
+ }
+ foreach ( $metadata as $key => &$field ) {
+ if ( $key === 'Software' || $key === 'Contact' ) {
+ // we skip some fields which have composite values. They are not particularly interesting
+ // and you can get them via the metadata / commonmetadata APIs anyway.
+ continue;
+ }
+ if ( isset( $field['value'] ) ) {
+ $field['value'] = $this->resolveMultivalueValue( $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 ) {
+ protected function sanitizeArrayForAPI( &$arr ) {
if ( !is_array( $arr ) ) {
return;
}
$counter = 1;
foreach ( $arr as $key => &$value ) {
- $sanitizedKey = $this->sanitizeKeyForXml( $key );
+ $sanitizedKey = $this->sanitizeKeyForAPI( $key );
if ( $sanitizedKey !== $key ) {
if ( isset( $arr[$sanitizedKey] ) ) {
// Make the sanitized keys hopefully unique.
@@ -1819,20 +1845,24 @@ class FormatMetadata extends ContextSource {
unset( $arr[$key] );
}
if ( is_array( $value ) ) {
- $this->sanitizeArrayForXml( $value );
+ $this->sanitizeArrayForAPI( $value );
}
}
+
+ // Handle API metadata keys (particularly "_type")
+ $keys = array_filter( array_keys( $arr ), 'ApiResult::isMetadataKey' );
+ if ( $keys ) {
+ ApiResult::setPreserveKeysList( $arr, $keys );
+ }
}
/**
- * 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.
+ * Turns a string into a valid API identifier.
* @param string $key
* @return string
* @since 1.23
*/
- protected function sanitizeKeyForXml( $key ) {
+ protected function sanitizeKeyForAPI( $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
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index 5992be11..e3621fbd 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -44,15 +44,16 @@ class GIFHandler extends BitmapHandler {
/**
* @param File $image
+ * @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image ) {
+ function formatMetadata( $image, $context = false ) {
$meta = $this->getCommonMetaArray( $image );
if ( count( $meta ) === 0 ) {
return false;
}
- return $this->formatMetadataHelper( $meta );
+ return $this->formatMetadataHelper( $meta, $context );
}
/**
diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php
index 178b0bf7..5c370465 100644
--- a/includes/media/GIFMetadataExtractor.php
+++ b/includes/media/GIFMetadataExtractor.php
@@ -158,7 +158,7 @@ class GIFMetadataExtractor {
// assume its that, otherwise assume its windows-1252 (iso-8859-1)
$dataCopy = $data;
// quickIsNFCVerify has the side effect of replacing any invalid characters
- UtfNormal::quickIsNFCVerify( $dataCopy );
+ UtfNormal\Validator::quickIsNFCVerify( $dataCopy );
if ( $dataCopy !== $data ) {
wfSuppressWarnings();
diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php
index 478249fe..0eb27cc8 100644
--- a/includes/media/IPTC.php
+++ b/includes/media/IPTC.php
@@ -456,7 +456,7 @@ class IPTC {
//treat as utf-8 if is valid utf-8. otherwise pretend its windows-1252
// most of the time if there is no 1:90 tag, it is either ascii, latin1, or utf-8
$oldData = $data;
- UtfNormal::quickIsNFCVerify( $data ); //make $data valid utf-8
+ UtfNormal\Validator::quickIsNFCVerify( $data ); //make $data valid utf-8
if ( $data === $oldData ) {
return $data; //if validation didn't change $data
} else {
diff --git a/includes/media/ImageHandler.php b/includes/media/ImageHandler.php
index 6dd0453e..968e4243 100644
--- a/includes/media/ImageHandler.php
+++ b/includes/media/ImageHandler.php
@@ -57,7 +57,7 @@ abstract class ImageHandler extends MediaHandler {
} elseif ( isset( $params['width'] ) ) {
$width = $params['width'];
} else {
- throw new MWException( 'No width specified to ' . __METHOD__ );
+ throw new MediaTransformInvalidParametersException( 'No width specified to ' . __METHOD__ );
}
# Removed for ProofreadPage
@@ -245,11 +245,11 @@ 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();
+ '<span class="mime-type">' . $file->getMimeType() . '</span>' )->parse();
} else {
$msg = wfMessage( 'file-info-size-pages' )->numParams( $file->getWidth(),
$file->getHeight() )->params( $size,
- $file->getMimeType() )->numParams( $pages )->parse();
+ '<span class="mime-type">' . $file->getMimeType() . '</span>' )->numParams( $pages )->parse();
}
return $msg;
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
index fbdbdfe3..5463922b 100644
--- a/includes/media/Jpeg.php
+++ b/includes/media/Jpeg.php
@@ -106,7 +106,7 @@ class JpegHandler extends ExifBitmapHandler {
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
return serialize( $meta );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// BitmapMetadataHandler throws an exception in certain exceptional
// cases like if file does not exist.
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
@@ -143,10 +143,8 @@ class JpegHandler extends ExifBitmapHandler {
" -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
" " . wfEscapeShellArg( $params['srcPath'] );
wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
- wfProfileIn( 'jpegtran' );
$retval = 0;
$err = wfShellExecWithStderr( $cmd, $retval );
- wfProfileOut( 'jpegtran' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
index aaa9930a..ae4af8d1 100644
--- a/includes/media/JpegMetadataExtractor.php
+++ b/includes/media/JpegMetadataExtractor.php
@@ -98,7 +98,7 @@ class JpegMetadataExtractor {
// First see if valid utf-8,
// if not try to convert it to windows-1252.
$com = $oldCom = trim( self::jpegExtractMarker( $fh ) );
- UtfNormal::quickIsNFCVerify( $com );
+ UtfNormal\Validator::quickIsNFCVerify( $com );
// turns $com to valid utf-8.
// thus if no change, its utf-8, otherwise its something else.
if ( $com !== $oldCom ) {
@@ -108,7 +108,7 @@ class JpegMetadataExtractor {
}
// Try it again, if its still not a valid string, then probably
// binary junk or some really weird encoding, so don't extract.
- UtfNormal::quickIsNFCVerify( $com );
+ UtfNormal\Validator::quickIsNFCVerify( $com );
if ( $com === $oldCom ) {
$segments["COM"][] = $oldCom;
} else {
diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php
index 64ca0115..33aed34f 100644
--- a/includes/media/MediaHandler.php
+++ b/includes/media/MediaHandler.php
@@ -162,7 +162,7 @@ abstract class MediaHandler {
*/
static function getMetadataVersion() {
$version = array( '2' ); // core metadata version
- wfRunHooks( 'GetMetadataVersion', array( &$version ) );
+ Hooks::run( 'GetMetadataVersion', array( &$version ) );
return implode( ';', $version );
}
@@ -507,9 +507,10 @@ abstract class MediaHandler {
* 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
+ * @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image ) {
+ function formatMetadata( $image, $context = false ) {
return false;
}
@@ -520,15 +521,16 @@ abstract class MediaHandler {
* This is used by the media handlers that use the FormatMetadata class
*
* @param array $metadataArray Metadata array
+ * @param bool|IContextSource $context Context to use (optional)
* @return array Array for use displaying metadata.
*/
- function formatMetadataHelper( $metadataArray ) {
+ function formatMetadataHelper( $metadataArray, $context = false ) {
$result = array(
'visible' => array(),
'collapsed' => array()
);
- $formatted = FormatMetadata::getFormattedData( $metadataArray );
+ $formatted = FormatMetadata::getFormattedData( $metadataArray, $context );
// Sort fields into visible and collapsed
$visibleFields = $this->visibleMetadataFields();
foreach ( $formatted as $name => $value ) {
@@ -637,7 +639,7 @@ abstract class MediaHandler {
*/
static function getGeneralLongDesc( $file ) {
return wfMessage( 'file-info' )->sizeParams( $file->getSize() )
- ->params( $file->getMimeType() )->parse();
+ ->params( '<span class="mime-type">' . $file->getMimeType() . '</span>' )->parse();
}
/**
@@ -859,4 +861,26 @@ abstract class MediaHandler {
public function sanitizeParamsForBucketing( $params ) {
return $params;
}
+
+ /**
+ * Gets configuration for the file warning message. Return value of
+ * the following structure:
+ * array(
+ * 'module' => 'example.filewarning.messages', // Required, module with messages loaded for the client
+ * 'messages' => array( // Required, array of names of messages
+ * 'main' => 'example-filewarning-main', // Required, main warning message
+ * 'header' => 'example-filewarning-header', // Optional, header for warning dialog
+ * 'footer' => 'example-filewarning-footer', // Optional, footer for warning dialog
+ * 'info' => 'example-filewarning-info', // Optional, text for more-information link (see below)
+ * ),
+ * 'link' => 'http://example.com', // Optional, link for more information
+ * )
+ *
+ * Returns null if no warning is necessary.
+ * @param File $file
+ * @return array|null
+ */
+ public function getWarningConfig( $file ) {
+ return null;
+ }
}
diff --git a/includes/media/MediaTransformInvalidParametersException.php b/includes/media/MediaTransformInvalidParametersException.php
new file mode 100644
index 00000000..15a2ca55
--- /dev/null
+++ b/includes/media/MediaTransformInvalidParametersException.php
@@ -0,0 +1,26 @@
+<?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 thrown by some methods when the transform parameter array is invalid
+ *
+ * @ingroup Exception
+ */
+class MediaTransformInvalidParametersException extends MWException {}
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index bc9e9173..1dd85191 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -348,9 +348,14 @@ class ThumbnailImage extends MediaTransformOutput {
throw new MWException( __METHOD__ . ' called in the old style' );
}
- $alt = empty( $options['alt'] ) ? '' : $options['alt'];
+ $alt = isset( $options['alt'] ) ? $options['alt'] : '';
- $query = empty( $options['desc-query'] ) ? '' : $options['desc-query'];
+ $query = isset( $options['desc-query'] ) ? $options['desc-query'] : '';
+
+ $attribs = array(
+ 'alt' => $alt,
+ 'src' => $this->url,
+ );
if ( !empty( $options['custom-url-link'] ) ) {
$linkAttribs = array( 'href' => $options['custom-url-link'] );
@@ -381,13 +386,11 @@ class ThumbnailImage extends MediaTransformOutput {
$linkAttribs = array( 'href' => $this->file->getURL() );
} else {
$linkAttribs = false;
+ if ( !empty( $options['title'] ) ) {
+ $attribs['title'] = $options['title'];
+ }
}
- $attribs = array(
- 'alt' => $alt,
- 'src' => $this->url,
- );
-
if ( empty( $options['no-dimensions'] ) ) {
$attribs['width'] = $this->width;
$attribs['height'] = $this->height;
@@ -410,7 +413,7 @@ class ThumbnailImage extends MediaTransformOutput {
$attribs['srcset'] = Html::srcSet( $this->responsiveUrls );
}
- wfRunHooks( 'ThumbnailBeforeProduceHTML', array( $this, &$attribs, &$linkAttribs ) );
+ Hooks::run( 'ThumbnailBeforeProduceHTML', array( $this, &$attribs, &$linkAttribs ) );
return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
}
@@ -474,3 +477,24 @@ class TransformParameterError extends MediaTransformError {
wfMessage( 'thumbnail_invalid_params' )->text() );
}
}
+
+/**
+ * Shortcut class for parameter file size errors
+ *
+ * @ingroup Media
+ * @since 1.25
+ */
+class TransformTooBigImageAreaError extends MediaTransformError {
+ function __construct( $params, $maxImageArea ) {
+ $msg = wfMessage( 'thumbnail_toobigimagearea' );
+
+ parent::__construct( 'thumbnail_error',
+ max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
+ max( isset( $params['height'] ) ? $params['height'] : 0, 120 ),
+ $msg->rawParams(
+ $msg->getLanguage()->formatComputingNumbers(
+ $maxImageArea, 1000, "size-$1pixel" )
+ )->text()
+ );
+ }
+}
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
index 7b3ddb51..5f1aca57 100644
--- a/includes/media/PNG.php
+++ b/includes/media/PNG.php
@@ -49,15 +49,16 @@ class PNGHandler extends BitmapHandler {
/**
* @param File $image
+ * @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image ) {
+ function formatMetadata( $image, $context = false ) {
$meta = $this->getCommonMetaArray( $image );
if ( count( $meta ) === 0 ) {
return false;
}
- return $this->formatMetadataHelper( $meta );
+ return $this->formatMetadataHelper( $meta, $context );
}
/**
@@ -174,7 +175,10 @@ class PNGHandler extends BitmapHandler {
return $wgLang->commaList( $info );
}
+ // PNGs should be easy to support, but it will need some sharpening applied
+ // and another user test to check if the perceived quality change is noticeable
+
public function supportsBucketing() {
- return true;
+ return false;
}
}
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index 74e5e048..8fdfa474 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -272,10 +272,8 @@ class SvgHandler extends ImageHandler {
$env['LANG'] = $lang;
}
- wfProfileIn( 'rsvg' );
wfDebug( __METHOD__ . ": $cmd\n" );
$err = wfShellExecWithStderr( $cmd, $retval, $env );
- wfProfileOut( 'rsvg' );
}
}
$removed = $this->removeBadFile( $dstPath, $retval );
@@ -364,7 +362,7 @@ class SvgHandler extends ImageHandler {
$metadata = array( 'version' => self::SVG_METADATA_VERSION );
try {
$metadata += SVGMetadataExtractor::getMetadata( $filename );
- } catch ( MWException $e ) { // @todo SVG specific exceptions
+ } catch ( Exception $e ) { // @todo SVG specific exceptions
// File not found, broken, etc.
$metadata['error'] = array(
'message' => $e->getMessage(),
@@ -412,9 +410,10 @@ class SvgHandler extends ImageHandler {
/**
* @param File $file
+ * @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $file ) {
+ function formatMetadata( $file, $context = false ) {
$result = array(
'visible' => array(),
'collapsed' => array()
@@ -488,7 +487,7 @@ class SvgHandler extends ImageHandler {
function makeParamString( $params ) {
$lang = '';
if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) {
- $params['lang'] = mb_strtolower( $params['lang'] );
+ $params['lang'] = strtolower( $params['lang'] );
$lang = "lang{$params['lang']}-";
}
if ( !isset( $params['width'] ) ) {
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index 2a1091d8..2037c331 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -138,7 +138,7 @@ class SVGReader {
$keepReading = $this->reader->read();
/* Skip until first element */
- while ( $keepReading && $this->reader->nodeType != XmlReader::ELEMENT ) {
+ while ( $keepReading && $this->reader->nodeType != XMLReader::ELEMENT ) {
$keepReading = $this->reader->read();
}
@@ -158,7 +158,7 @@ class SVGReader {
$this->debug( "$tag" );
- if ( $isSVG && $tag == 'svg' && $type == XmlReader::END_ELEMENT
+ if ( $isSVG && $tag == 'svg' && $type == XMLReader::END_ELEMENT
&& $this->reader->depth <= $exitDepth
) {
break;
@@ -166,7 +166,7 @@ class SVGReader {
$this->readField( $tag, 'title' );
} elseif ( $isSVG && $tag == 'desc' ) {
$this->readField( $tag, 'description' );
- } elseif ( $isSVG && $tag == 'metadata' && $type == XmlReader::ELEMENT ) {
+ } elseif ( $isSVG && $tag == 'metadata' && $type == XMLReader::ELEMENT ) {
$this->readXml( $tag, 'metadata' );
} elseif ( $isSVG && $tag == 'script' ) {
// We normally do not allow scripted svgs.
@@ -199,17 +199,17 @@ class SVGReader {
*/
private function readField( $name, $metafield = null ) {
$this->debug( "Read field $metafield" );
- if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
+ if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) {
return;
}
$keepReading = $this->reader->read();
while ( $keepReading ) {
if ( $this->reader->localName == $name
&& $this->reader->namespaceURI == self::NS_SVG
- && $this->reader->nodeType == XmlReader::END_ELEMENT
+ && $this->reader->nodeType == XMLReader::END_ELEMENT
) {
break;
- } elseif ( $this->reader->nodeType == XmlReader::TEXT ) {
+ } elseif ( $this->reader->nodeType == XMLReader::TEXT ) {
$this->metadata[$metafield] = trim( $this->reader->value );
}
$keepReading = $this->reader->read();
@@ -224,7 +224,7 @@ class SVGReader {
*/
private function readXml( $metafield = null ) {
$this->debug( "Read top level metadata" );
- if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) {
+ if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) {
return;
}
// @todo Find and store type of xml snippet. metadata['metadataType'] = "rdf"
@@ -246,7 +246,7 @@ class SVGReader {
*/
private function animateFilterAndLang( $name ) {
$this->debug( "animate filter for tag $name" );
- if ( $this->reader->nodeType != XmlReader::ELEMENT ) {
+ if ( $this->reader->nodeType != XMLReader::ELEMENT ) {
return;
}
if ( $this->reader->isEmptyElement ) {
@@ -256,11 +256,11 @@ 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
+ && $this->reader->nodeType == XMLReader::ELEMENT
) {
$sysLang = $this->reader->getAttribute( 'systemLanguage' );
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
index bea6cab3..750528f0 100644
--- a/includes/media/Tiff.php
+++ b/includes/media/Tiff.php
@@ -88,7 +88,7 @@ class TiffHandler extends ExifBitmapHandler {
$meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
return serialize( $meta );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// BitmapMetadataHandler throws an exception in certain exceptional
// cases like if file does not exist.
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
diff --git a/includes/media/TransformationalImageHandler.php b/includes/media/TransformationalImageHandler.php
index 3e3be3d1..fd8d81d2 100644
--- a/includes/media/TransformationalImageHandler.php
+++ b/includes/media/TransformationalImageHandler.php
@@ -61,29 +61,6 @@ abstract class TransformationalImageHandler extends ImageHandler {
}
}
- # 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;
}
@@ -190,6 +167,11 @@ abstract class TransformationalImageHandler extends ImageHandler {
return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
+ if ( !$this->isImageAreaOkForThumbnaling( $image, $params ) ) {
+ global $wgMaxImageArea;
+ return new TransformTooBigImageAreaError( $params, $wgMaxImageArea );
+ }
+
if ( $flags & self::TRANSFORM_LATER ) {
wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
$newParams = array(
@@ -216,6 +198,12 @@ abstract class TransformationalImageHandler extends ImageHandler {
# Transform functions and binaries need a FS source file
$thumbnailSource = $this->getThumbnailSource( $image, $params );
+ // If the source isn't the original, disable EXIF rotation because it's already been applied
+ if ( $scalerParams['srcWidth'] != $thumbnailSource['width']
+ || $scalerParams['srcHeight'] != $thumbnailSource['height'] ) {
+ $scalerParams['disableRotation'] = true;
+ }
+
$scalerParams['srcPath'] = $thumbnailSource['path'];
$scalerParams['srcWidth'] = $thumbnailSource['width'];
$scalerParams['srcHeight'] = $thumbnailSource['height'];
@@ -234,7 +222,7 @@ abstract class TransformationalImageHandler extends ImageHandler {
# Try a hook. Called "Bitmap" for historical reasons.
/** @var $mto MediaTransformOutput */
$mto = null;
- wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
+ Hooks::run( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
if ( !is_null( $mto ) ) {
wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
$scaler = 'hookaborted';
@@ -590,4 +578,43 @@ abstract class TransformationalImageHandler extends ImageHandler {
public function mustRender( $file ) {
return $this->canRotate() && $this->getRotation( $file ) != 0;
}
+
+ /**
+ * Check if the file is smaller than the maximum image area for thumbnailing.
+ *
+ * Runs the 'BitmapHandlerCheckImageArea' hook.
+ *
+ * @param File $file
+ * @param array $params
+ * @return bool
+ * @since 1.25
+ */
+ public function isImageAreaOkForThumbnaling( $file, &$params ) {
+ global $wgMaxImageArea;
+
+ # For historical reasons, hook starts with BitmapHandler
+ $checkImageAreaHookResult = null;
+ Hooks::run(
+ 'BitmapHandlerCheckImageArea',
+ array( $file, &$params, &$checkImageAreaHookResult )
+ );
+
+ if ( !is_null( $checkImageAreaHookResult ) ) {
+ // was set by hook, so return that value
+ return (bool)$checkImageAreaHookResult;
+ }
+
+ $srcWidth = $file->getWidth( $params['page'] );
+ $srcHeight = $file->getHeight( $params['page'] );
+
+ if ( $srcWidth * $srcHeight > $wgMaxImageArea
+ && !( $file->getMimeType() == 'image/jpeg'
+ && $this->getScalerType( false, false ) == 'im' )
+ ) {
+ # Only ImageMagick can efficiently downsize jpg images without loading
+ # the entire file in memory
+ return false;
+ }
+ return true;
+ }
}
diff --git a/includes/media/XCF.php b/includes/media/XCF.php
index 48b7a47c..6544d5cf 100644
--- a/includes/media/XCF.php
+++ b/includes/media/XCF.php
@@ -130,7 +130,7 @@ class XCFHandler extends BitmapHandler {
"/Nbase_type", # /
$binaryHeader
);
- } catch ( MWException $mwe ) {
+ } catch ( Exception $mwe ) {
return false;
}
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
index a3f45e6c..50f04ae9 100644
--- a/includes/media/XMP.php
+++ b/includes/media/XMP.php
@@ -195,7 +195,7 @@ class XMPReader {
$data = $this->results;
- wfRunHooks( 'XMPGetResults', array( &$data ) );
+ Hooks::run( 'XMPGetResults', array( &$data ) );
if ( isset( $data['xmp-special']['AuthorsPosition'] )
&& is_string( $data['xmp-special']['AuthorsPosition'] )
@@ -331,7 +331,7 @@ class XMPReader {
// could declare entities unsafe to parse with xml_parse (T85848/T71210).
if ( $this->parsable !== self::PARSABLE_OK ) {
if ( $this->parsable === self::PARSABLE_NO ) {
- throw new MWException( 'Unsafe doctype declaration in XML.' );
+ throw new Exception( 'Unsafe doctype declaration in XML.' );
}
$content = $this->xmlParsableBuffer . $content;
@@ -344,7 +344,7 @@ class XMPReader {
$msg = ( $this->parsable === self::PARSABLE_NO ) ?
'Unsafe doctype declaration in XML.' :
'No root element found in XML.';
- throw new MWException( $msg );
+ throw new Exception( $msg );
}
}
@@ -359,7 +359,7 @@ class XMPReader {
$this->results = array(); // blank if error.
return false;
}
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
wfDebugLog( 'XMP', 'XMP parse error: ' . $e );
$this->results = array();
@@ -501,6 +501,10 @@ class XMPReader {
);
$oldDisable = libxml_disable_entity_loader( true );
+ $reset = new ScopedCallback(
+ 'libxml_disable_entity_loader',
+ array( $oldDisable )
+ );
$reader->setParserProperty( XMLReader::SUBST_ENTITIES, false );
// Even with LIBXML_NOWARNING set, XMLReader::read gives a warning
@@ -520,7 +524,6 @@ class XMPReader {
}
}
wfRestoreWarnings();
- libxml_disable_entity_loader( $oldDisable );
if ( !is_null( $result ) ) {
return $result;
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
index 7e47ec14..e0a491cb 100644
--- a/includes/media/XMPInfo.php
+++ b/includes/media/XMPInfo.php
@@ -34,7 +34,7 @@ class XMPInfo {
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 ) );
+ Hooks::run( 'XMPGetInfo', array( &self::$items ) );
self::$ranHooks = true; // Only want to do this once.
}
diff --git a/includes/mime.info b/includes/mime.info
index 07b24954..243e2802 100644
--- a/includes/mime.info
+++ b/includes/mime.info
@@ -72,6 +72,7 @@ text/tab-separated-values [TEXT]
application/zip application/x-zip [ARCHIVE]
application/x-gzip [ARCHIVE]
application/x-bzip [ARCHIVE]
+application/x-bzip2 [ARCHIVE]
application/x-tar [ARCHIVE]
application/x-stuffit [ARCHIVE]
application/x-opc+zip [ARCHIVE]
diff --git a/includes/mime.types b/includes/mime.types
index 75017db2..210610ad 100644
--- a/includes/mime.types
+++ b/includes/mime.types
@@ -21,7 +21,8 @@ application/vnd.wap.wmlc wmlc
application/vnd.wap.wmlscriptc wmlsc
application/voicexml+xml vxml
application/x-bcpio bcpio
-application/x-bzip gz bz2
+application/x-bzip bz
+application/x-bzip2 bz2
application/x-cdlink vcd
application/x-chess-pgn pgn
application/x-cpio cpio
diff --git a/includes/normal/Makefile b/includes/normal/Makefile
deleted file mode 100644
index 76cb68ba..00000000
--- a/includes/normal/Makefile
+++ /dev/null
@@ -1,78 +0,0 @@
-.PHONY : all test testutf8 testclean icutest bench icubench clean distclean
-
-## Latest greatest version of Unicode
-## May cause confusion if running test suite from these files
-## when the data was generated from a previous version.
-#BASE=http://www.unicode.org/Public/UNIDATA
-
-# Explicitly using Unicode 6.0
-BASE=http://www.unicode.org/Public/6.0.0/ucd
-
-# Can override to php-cli or php5 or whatever
-PHP=php
-#PHP=php-cli
-
-# Some nice tool to grab URLs with
-FETCH=wget
-#FETCH=fetch
-
-all : UtfNormalData.inc
-
-UtfNormalData.inc : UtfNormalGenerate.php UtfNormalUtil.php UnicodeData.txt CompositionExclusions.txt NormalizationCorrections.txt DerivedNormalizationProps.txt
- $(PHP) UtfNormalGenerate.php
-
-test : testutf8 UtfNormalTest.php UtfNormalData.inc NormalizationTest.txt
- $(PHP) UtfNormalTest.php
-
-testutf8 : Utf8Test.php UTF-8-test.txt
- $(PHP) Utf8Test.php
-
-bench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/young.txt testdata/bulgakov.txt
- $(PHP) UtfNormalBench.php
-
-icutest : UtfNormalData.inc NormalizationTest.txt
- $(PHP) Utf8Test.php --icu
- $(PHP) UtfNormalTest.php --icu
-
-icubench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/young.txt testdata/bulgakov.txt
- $(PHP) UtfNormalBench.php --icu
-
-clean :
- rm -f UtfNormalData.inc UtfNormalDataK.inc
-
-distclean : clean
- rm -f CompositionExclusions.txt NormalizationTest.txt NormalizationCorrections.txt UnicodeData.txt DerivedNormalizationProps.txt UTF-8-test.txt
-
-# The Unicode data files...
-CompositionExclusions.txt :
- $(FETCH) $(BASE)/CompositionExclusions.txt
-
-NormalizationTest.txt :
- $(FETCH) $(BASE)/NormalizationTest.txt
-
-NormalizationCorrections.txt :
- $(FETCH) $(BASE)/NormalizationCorrections.txt
-
-DerivedNormalizationProps.txt :
- $(FETCH) $(BASE)/DerivedNormalizationProps.txt
-
-UnicodeData.txt :
- $(FETCH) $(BASE)/UnicodeData.txt
-
-UTF-8-test.txt :
- $(FETCH) http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
-
-testdata/berlin.txt :
- mkdir -p testdata && wget -U MediaWiki/test -O testdata/berlin.txt "http://de.wikipedia.org/w/index.php?title=Berlin&oldid=2775712&action=raw"
-
-testdata/washington.txt :
- mkdir -p testdata && wget -U MediaWiki/test -O testdata/washington.txt "http://en.wikipedia.org/w/index.php?title=Washington%2C_D.C.&oldid=6370218&action=raw"
-
-testdata/tokyo.txt :
- mkdir -p testdata && wget -U MediaWiki/test -O testdata/tokyo.txt "http://ja.wikipedia.org/w/index.php?title=%E6%9D%B1%E4%BA%AC%E9%83%BD&oldid=940880&action=raw"
-
-testdata/young.txt :
- mkdir -p testdata && wget -U MediaWiki/test -O testdata/young.txt "http://ko.wikipedia.org/w/index.php?title=%EC%9D%B4%EC%88%98%EC%98%81&oldid=627688&action=raw"
-
-testdata/bulgakov.txt :
- mkdir -p testdata && wget -U MediaWiki/test -O testdata/bulgakov.txt "http://ru.wikipedia.org/w/index.php?title=%D0%91%D1%83%D0%BB%D0%B3%D0%B0%D0%BA%D0%BE%D0%B2%2C_%D0%A1%D0%B5%D1%80%D0%B3%D0%B5%D0%B9_%D0%9D%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D0%B5%D0%B2%D0%B8%D1%87&oldid=17704&action=raw"
diff --git a/includes/normal/README b/includes/normal/README
deleted file mode 100644
index 0f718d2c..00000000
--- a/includes/normal/README
+++ /dev/null
@@ -1,59 +0,0 @@
-This directory contains some Unicode normalization routines. These routines
-are meant to be reusable in other projects, so I'm not tying them to the
-MediaWiki utility functions.
-
-The main function to care about is UtfNormal::toNFC(); this will convert
-a given UTF-8 string to Normalization Form C if it's not already such.
-The function assumes that the input string is already valid UTF-8; if there
-are corrupt characters this may produce erroneous results.
-
-To also check for illegal characters, use UtfNormal::cleanUp(). This will
-strip illegal UTF-8 sequences and characters that are illegal in XML, and
-if necessary convert to normalization form C.
-
-Performance is kind of stinky in absolute terms, though it should be speedy
-on pure ASCII text. ;) On text that can be determined quickly to already be
-in NFC it's not too awful but it can quickly get uncomfortably slow,
-particularly for Korean text (the hangul decomposition/composition code is
-extra slow).
-
-
-== Regenerating data tables ==
-
-UtfNormalData.inc and UtfNormalDataK.inc are generated from the Unicode
-Character Database by the script UtfNormalGenerate.php. On a *nix system
-'make' should fetch the necessary files and regenerate it if the scripts
-have been changed or you remove it.
-
-
-== Testing ==
-
-'make test' will run the conformance test (UtfNormalTest.php), fetching the
-data from from the net if necessary. If it reports failure, something is
-going wrong!
-
-You may have to set up PHPUnit first.
-
-$ pear channel-discover pear.phpunit.de
-$ pear install phpunit/PHPUnit
-
-== Benchmarks ==
-
-Run 'make bench' to download some sample texts from Wikipedia and run some
-cheap benchmarks of some of the functions. Take all numbers with large
-grains of salt.
-
-
-== PHP module extension ==
-
-There's an experimental PHP extension module which wraps the ICU library's
-normalization functions. This is *MUCH* faster than doing this work in pure
-PHP code. This is at https://git.wikimedia.org/summary/mediawiki%2Fextensions%2Fnormal.git.
-It is used by the WMF, which currently runs PHP 5.3.10 on Linux. It hasn't been
-thoroughly tested on other configurations, but may work.
-
-If the php_normal.so module is loaded in php.ini, the normalization functions
-will automatically use it. If you can't (or don't want to) load it in php.ini,
-you may be able to load it using the dl() function before the inclusion of
-UtfNormal.php, and it will be picked up.
-
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
deleted file mode 100644
index 0604d7bb..00000000
--- a/includes/normal/RandomTest.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-/**
- * Test feeds random 16-byte strings to both the pure PHP and ICU-based
- * UtfNormal::cleanUp() code paths, and checks to see if there's a
- * difference. Will run forever until it finds one or you kill it.
- *
- * Copyright (C) 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup UtfNormal
- */
-
-if ( PHP_SAPI != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
-/** */
-require_once 'UtfNormal.php';
-require_once '../diff/DifferenceEngine.php';
-
-dl( 'php_utfnormal.so' );
-
-# mt_srand( 99999 );
-
-function randomString( $length, $nullOk, $ascii = false ) {
- $out = '';
- for ( $i = 0; $i < $length; $i++ )
- $out .= chr( mt_rand( $nullOk ? 0 : 1, $ascii ? 127 : 255 ) );
-
- return $out;
-}
-
-/* Duplicate of the cleanUp() path for ICU usage */
-function donorm( $str ) {
- # We exclude a few chars that ICU would not.
- $str = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', UTF8_REPLACEMENT, $str );
- $str = str_replace( UTF8_FFFE, UTF8_REPLACEMENT, $str );
- $str = str_replace( UTF8_FFFF, UTF8_REPLACEMENT, $str );
-
- # 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( $str . "\x01", UtfNormal::UNORM_NFC ), "\x01" );
-}
-
-function showDiffs( $a, $b ) {
- $ota = explode( "\n", str_replace( "\r\n", "\n", $a ) );
- $nta = explode( "\n", str_replace( "\r\n", "\n", $b ) );
-
- $diffs = new Diff( $ota, $nta );
- $formatter = new TableDiffFormatter();
- $funky = $formatter->format( $diffs );
- $matches = array();
- preg_match_all( '/<(?:ins|del) class="diffchange">(.*?)<\/(?:ins|del)>/', $funky, $matches );
- foreach ( $matches[1] as $bit ) {
- $hex = bin2hex( $bit );
- echo "\t$hex\n";
- }
-}
-
-$size = 16;
-$n = 0;
-while ( true ) {
- $n++;
- echo "$n\n";
-
- $str = randomString( $size, true );
- $clean = UtfNormal::cleanUp( $str );
- $norm = donorm( $str );
-
- echo strlen( $clean ) . ", " . strlen( $norm );
- if ( $clean == $norm ) {
- echo " (match)\n";
- } else {
- echo " (FAIL)\n";
- echo "\traw: " . bin2hex( $str ) . "\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/Utf8Test.php b/includes/normal/Utf8Test.php
deleted file mode 100644
index f4acc1eb..00000000
--- a/includes/normal/Utf8Test.php
+++ /dev/null
@@ -1,156 +0,0 @@
-<?php
-/**
- * Runs the UTF-8 decoder test at:
- * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup UtfNormal
- */
-
-/** */
-
-if ( PHP_SAPI != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
-require_once 'UtfNormalDefines.php';
-require_once 'UtfNormalUtil.php';
-require_once 'UtfNormal.php';
-mb_internal_encoding( "utf-8" );
-
-$verbose = false;
-#$verbose = true;
-
-$in = fopen( "UTF-8-test.txt", "rt" );
-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 );
-}
-
-$columns = 0;
-while ( false !== ( $line = fgets( $in ) ) ) {
- $matches = array();
- if ( preg_match( '/^(Here come the tests:\s*)\|$/', $line, $matches ) ) {
- $columns = strpos( $line, '|' );
- break;
- }
-}
-
-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 );
-}
-
-# print "$columns\n";
-
-$ignore = array(
- # These two lines actually seem to be corrupt
- '2.1.1', '2.2.1' );
-
-$exceptions = array(
- # Tests that should mark invalid characters due to using long
- # sequences beyond what is now considered legal.
- '2.1.5', '2.1.6', '2.2.4', '2.2.5', '2.2.6', '2.3.5',
-
- # Literal 0xffff, which is illegal
- '2.2.3' );
-
-$longTests = array(
- # These tests span multiple lines
- '3.1.9', '3.2.1', '3.2.2', '3.2.3', '3.2.4', '3.2.5',
- '3.4' );
-
-# These tests are not in proper subsections
-$sectionTests = array( '3.4' );
-
-$section = null;
-$test = '';
-$failed = 0;
-$success = 0;
-$total = 0;
-while ( false !== ( $line = fgets( $in ) ) ) {
- $matches = array();
- if ( preg_match( '/^(\d+)\s+(.*?)\s*\|/', $line, $matches ) ) {
- $section = $matches[1];
- print $line;
- continue;
- }
- if ( preg_match( '/^(\d+\.\d+\.\d+)\s*/', $line, $matches ) ) {
- $test = $matches[1];
-
- if ( in_array( $test, $ignore ) ) {
- continue;
- }
- if ( in_array( $test, $longTests ) ) {
- $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 {
- testLine( $test, $line, $total, $success, $failed, $columns, $exceptions, $verbose );
- }
- }
-}
-
-if ( $failed ) {
- echo "\nFailed $failed tests.\n";
- echo "UTF-8 DECODER TEST FAILED\n";
- exit ( -1 );
-}
-
-echo "UTF-8 DECODER TEST SUCCESS!\n";
-exit ( 0 );
-
-function testLine( $test, $line, &$total, &$success, &$failed, $columns, $exceptions, $verbose ) {
- $stripped = $line;
- UtfNormal::quickisNFCVerify( $stripped );
-
- $same = ( $line == $stripped );
- $len = mb_strlen( substr( $stripped, 0, strpos( $stripped, '|' ) ) );
- if ( $len == 0 ) {
- $len = strlen( substr( $stripped, 0, strpos( $stripped, '|' ) ) );
- }
-
- $ok = $same ^ ( $test >= 3 );
-
- $ok ^= in_array( $test, $exceptions );
-
- $ok &= ( $columns == $len );
-
- $total++;
- if ( $ok ) {
- $success++;
- } else {
- $failed++;
- }
-
- if ( $verbose || !$ok ) {
- print str_replace( "\n", "$len\n", $stripped );
- }
-}
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
deleted file mode 100644
index 8204f974..00000000
--- a/includes/normal/UtfNormal.php
+++ /dev/null
@@ -1,790 +0,0 @@
-<?php
-/**
- * Unicode normalization routines
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup UtfNormal
- */
-
-/**
- * @defgroup UtfNormal UtfNormal
- */
-
-define( 'NORMALIZE_ICU', function_exists( 'utf8_normalize' ) );
-define( 'NORMALIZE_INTL', function_exists( 'normalizer_normalize' ) );
-
-/**
- * Unicode normalization routines for working with UTF-8 strings.
- * Currently assumes that input strings are valid UTF-8!
- *
- * Not as fast as I'd like, but should be usable for most purposes.
- * UtfNormal::toNFC() will bail early if given ASCII text or text
- * it can quickly determine is already normalized.
- *
- * All functions can be called static.
- *
- * See description of forms at http://www.unicode.org/reports/tr15/
- *
- * @ingroup UtfNormal
- */
-class UtfNormal {
- /**
- * For using the ICU wrapper
- */
- const UNORM_NONE = 1;
- const UNORM_NFD = 2;
- const UNORM_NFKD = 3;
- const UNORM_NFC = 4;
- const UNORM_NFKC = 5;
- const UNORM_FCD = 6;
- const UNORM_DEFAULT = self::UNORM_NFC;
-
- public static $utfCombiningClass = null;
- public static $utfCanonicalComp = null;
- public static $utfCanonicalDecomp = null;
-
- # Load compatibility decompositions on demand if they are needed.
- public static $utfCompatibilityDecomp = null;
- public static $utfCheckNFC;
-
- /**
- * The ultimate convenience function! Clean up invalid UTF-8 sequences,
- * and convert to normal form C, canonical composition.
- *
- * Fast return for pure ASCII strings; some lesser optimizations for
- * strings containing only known-good characters. Not as fast as toNFC().
- *
- * @param string $string a UTF-8 string
- * @return string a clean, shiny, normalized UTF-8 string
- */
- static function cleanUp( $string ) {
- 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 ) {
- $string = self::replaceForNativeNormalize( $string );
- $norm = normalizer_normalize( $string, Normalizer::FORM_C );
- 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 that's true, the string is actually already normal.
- return $string;
- } else {
- # Now we are valid but non-normal
- return normalizer_normalize( $string, Normalizer::FORM_C );
- }
- } else {
- return $norm;
- }
- } elseif ( UtfNormal::quickIsNFCVerify( $string ) ) {
- # Side effect -- $string has had UTF-8 errors cleaned up.
- return $string;
- } else {
- return UtfNormal::NFC( $string );
- }
- }
-
- /**
- * Convert a UTF-8 string to normal form C, canonical composition.
- * Fast return for pure ASCII strings; some lesser optimizations for
- * strings containing only known-good characters.
- *
- * @param string $string a valid UTF-8 string. Input is not validated.
- * @return string a UTF-8 string in normal form C
- */
- static function toNFC( $string ) {
- if ( NORMALIZE_INTL )
- return normalizer_normalize( $string, Normalizer::FORM_C );
- elseif ( NORMALIZE_ICU )
- return utf8_normalize( $string, self::UNORM_NFC );
- elseif ( UtfNormal::quickIsNFC( $string ) )
- return $string;
- else
- return UtfNormal::NFC( $string );
- }
-
- /**
- * Convert a UTF-8 string to normal form D, canonical decomposition.
- * Fast return for pure ASCII strings.
- *
- * @param string $string a valid UTF-8 string. Input is not validated.
- * @return string a UTF-8 string in normal form D
- */
- static function toNFD( $string ) {
- if ( NORMALIZE_INTL )
- return normalizer_normalize( $string, Normalizer::FORM_D );
- elseif ( NORMALIZE_ICU )
- return utf8_normalize( $string, self::UNORM_NFD );
- elseif ( preg_match( '/[\x80-\xff]/', $string ) )
- return UtfNormal::NFD( $string );
- else
- return $string;
- }
-
- /**
- * Convert a UTF-8 string to normal form KC, compatibility composition.
- * This may cause irreversible information loss, use judiciously.
- * Fast return for pure ASCII strings.
- *
- * @param string $string a valid UTF-8 string. Input is not validated.
- * @return string a UTF-8 string in normal form KC
- */
- static function toNFKC( $string ) {
- if ( NORMALIZE_INTL )
- return normalizer_normalize( $string, Normalizer::FORM_KC );
- elseif ( NORMALIZE_ICU )
- return utf8_normalize( $string, self::UNORM_NFKC );
- elseif ( preg_match( '/[\x80-\xff]/', $string ) )
- return UtfNormal::NFKC( $string );
- else
- return $string;
- }
-
- /**
- * Convert a UTF-8 string to normal form KD, compatibility decomposition.
- * This may cause irreversible information loss, use judiciously.
- * Fast return for pure ASCII strings.
- *
- * @param string $string a valid UTF-8 string. Input is not validated.
- * @return string a UTF-8 string in normal form KD
- */
- static function toNFKD( $string ) {
- if ( NORMALIZE_INTL )
- return normalizer_normalize( $string, Normalizer::FORM_KD );
- elseif ( NORMALIZE_ICU )
- return utf8_normalize( $string, self::UNORM_NFKD );
- elseif ( preg_match( '/[\x80-\xff]/', $string ) )
- return UtfNormal::NFKD( $string );
- else
- return $string;
- }
-
- /**
- * Load the basic composition data if necessary
- * @private
- */
- static function loadData() {
- if ( !isset( self::$utfCombiningClass ) ) {
- require_once __DIR__ . '/UtfNormalData.inc';
- }
- }
-
- /**
- * Returns true if the string is _definitely_ in NFC.
- * Returns false if not or uncertain.
- * @param string $string a valid UTF-8 string. Input is not validated.
- * @return bool
- */
- 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;
-
- UtfNormal::loadData();
- $len = strlen( $string );
- for ( $i = 0; $i < $len; $i++ ) {
- $c = $string[$i];
- $n = ord( $c );
- if ( $n < 0x80 ) {
- continue;
- } elseif ( $n >= 0xf0 ) {
- $c = substr( $string, $i, 4 );
- $i += 3;
- } elseif ( $n >= 0xe0 ) {
- $c = substr( $string, $i, 3 );
- $i += 2;
- } elseif ( $n >= 0xc0 ) {
- $c = substr( $string, $i, 2 );
- $i++;
- }
- if ( isset( self::$utfCheckNFC[$c] ) ) {
- # If it's NO or MAYBE, bail and do the slow check.
- return false;
- }
- if ( isset( self::$utfCombiningClass[$c] ) ) {
- # Combining character? We might have to do sorting, at least.
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Returns true if the string is _definitely_ in NFC.
- * Returns false if not or uncertain.
- * @param string $string a UTF-8 string, altered on output to be valid UTF-8 safe for XML.
- * @return bool
- */
- static function quickIsNFCVerify( &$string ) {
- # Screen out some characters that eg won't be allowed in XML
- $string = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', UTF8_REPLACEMENT, $string );
-
- # 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;
-
- static $checkit = null, $tailBytes = null, $utfCheckOrCombining = null;
- if ( !isset( $checkit ) ) {
- # Load/build some scary lookup tables...
- UtfNormal::loadData();
-
- $utfCheckOrCombining = array_merge( self::$utfCheckNFC, self::$utfCombiningClass );
-
- # 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 ) ) );
-
- # 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 ) {
- $remaining = 0;
- } elseif ( $n < 0xe0 ) {
- $remaining = 1;
- } elseif ( $n < 0xf0 ) {
- $remaining = 2;
- } elseif ( $n < 0xf8 ) {
- $remaining = 3;
- } elseif ( $n < 0xfc ) {
- $remaining = 4;
- } elseif ( $n < 0xfe ) {
- $remaining = 5;
- } else {
- $remaining = 0;
- }
- $tailBytes[chr( $n )] = $remaining;
- }
- }
-
- # Chop the text into pure-ASCII and non-ASCII areas;
- # large ASCII parts can be handled much more quickly.
- # Don't chop up Unicode areas for punctuation, though,
- # that wastes energy.
- $matches = array();
- preg_match_all(
- '/([\x00-\x7f]+|[\x80-\xff][\x00-\x40\x5b-\x5f\x7b-\xff]*)/',
- $string, $matches );
-
- $looksNormal = true;
- $base = 0;
- $replace = array();
- foreach ( $matches[1] as $str ) {
- $chunk = strlen( $str );
-
- if ( $str[0] < "\x80" ) {
- # ASCII chunk: guaranteed to be valid UTF-8
- # and in normal form C, so skip over it.
- $base += $chunk;
- continue;
- }
-
- # We'll have to examine the chunk byte by byte to ensure
- # that it consists of valid UTF-8 sequences, and to see
- # if any of them might not be normalized.
- #
- # Since PHP is not the fastest language on earth, some of
- # this code is a little ugly with inner loop optimizations.
-
- $head = '';
- $len = $chunk + 1; # Counting down is faster. I'm *so* sorry.
-
- for ( $i = -1; --$len; ) {
- $remaining = $tailBytes[$c = $str[++$i]];
- 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" ) {
- # Legal tail bytes are nice.
- $sequence .= $c;
- } else {
- 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 ) );
- break 2;
- } else {
- # Illegal tail byte; abandon the sequence.
- $replace[] = array( UTF8_REPLACEMENT,
- $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;
- ++$len;
- continue 2;
- }
- }
- } while ( --$remaining );
-
- if ( isset( $checkit[$head] ) ) {
- # Do some more detailed validity checks, for
- # invalid characters and illegal sequences.
- 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 ) {
- # 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 ) );
- $head = '';
- continue;
- }
- } else {
- # Slower, but rarer checks...
- $n = ord( $head );
- 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 )
-
- # U+FFFE and U+FFFF are explicitly forbidden in Unicode.
- || ( $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 )
- ) {
-
- $replace[] = array( UTF8_REPLACEMENT,
- $base + $i + 1 - strlen( $sequence ),
- strlen( $sequence ) );
- $head = '';
- continue;
- }
- }
- }
-
- 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.
- $looksNormal = false;
- }
-
- # The sequence is legal!
- $head = '';
- } elseif ( $c < "\x80" ) {
- # ASCII byte.
- $head = '';
- } elseif ( $c < "\xc0" ) {
- # Illegal tail bytes
- if ( $head == '' ) {
- # Out of the blue!
- $replace[] = array( UTF8_REPLACEMENT, $base + $i, 1 );
- } else {
- # Don't add if we're continuing a broken sequence;
- # we already put a replacement character when we looked
- # at the broken sequence.
- $replace[] = array( '', $base + $i, 1 );
- }
- } else {
- # Miscellaneous freaks.
- $replace[] = array( UTF8_REPLACEMENT, $base + $i, 1 );
- $head = '';
- }
- }
- $base += $chunk;
- }
- if ( count( $replace ) ) {
- # There were illegal UTF-8 sequences we need to fix up.
- $out = '';
- $last = 0;
- foreach ( $replace as $rep ) {
- list( $replacement, $start, $length ) = $rep;
- if ( $last < $start ) {
- $out .= substr( $string, $last, $start - $last );
- }
- $out .= $replacement;
- $last = $start + $length;
- }
- if ( $last < strlen( $string ) ) {
- $out .= substr( $string, $last );
- }
- $string = $out;
- }
-
- return $looksNormal;
- }
-
- # These take a string and run the normalization on them, without
- # checking for validity or any optimization etc. Input must be
- # VALID UTF-8!
- /**
- * @param $string string
- * @return string
- * @private
- */
- static function NFC( $string ) {
- return UtfNormal::fastCompose( UtfNormal::NFD( $string ) );
- }
-
- /**
- * @param $string string
- * @return string
- * @private
- */
- static function NFD( $string ) {
- UtfNormal::loadData();
-
- return UtfNormal::fastCombiningSort(
- UtfNormal::fastDecompose( $string, self::$utfCanonicalDecomp ) );
- }
-
- /**
- * @param $string string
- * @return string
- * @private
- */
- static function NFKC( $string ) {
- return UtfNormal::fastCompose( UtfNormal::NFKD( $string ) );
- }
-
- /**
- * @param $string string
- * @return string
- * @private
- */
- static function NFKD( $string ) {
- 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).
- * Input is assumed to be *valid* UTF-8. Invalid code will break.
- * @private
- * @param string $string valid UTF-8 string
- * @param array $map hash of expanded decomposition map
- * @return string a UTF-8 string decomposed, not yet normalized (needs sorting)
- */
- static function fastDecompose( $string, $map ) {
- UtfNormal::loadData();
- $len = strlen( $string );
- $out = '';
- for ( $i = 0; $i < $len; $i++ ) {
- $c = $string[$i];
- $n = ord( $c );
- if ( $n < 0x80 ) {
- # ASCII chars never decompose
- # THEY ARE IMMORTAL
- $out .= $c;
- continue;
- } elseif ( $n >= 0xf0 ) {
- $c = substr( $string, $i, 4 );
- $i += 3;
- } elseif ( $n >= 0xe0 ) {
- $c = substr( $string, $i, 3 );
- $i += 2;
- } elseif ( $n >= 0xc0 ) {
- $c = substr( $string, $i, 2 );
- $i++;
- }
- if ( isset( $map[$c] ) ) {
- $out .= $map[$c];
- continue;
- } else {
- 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;
- $l = intval( $index / UNICODE_HANGUL_NCOUNT );
- $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 ) {
- $out .= "\xe1\x87" . chr( 0x80 + $t - 25 );
- } elseif ( $t ) {
- $out .= "\xe1\x86" . chr( 0xa7 + $t );
- }
- continue;
- }
- }
- $out .= $c;
- }
-
- return $out;
- }
-
- /**
- * Sorts combining characters into canonical order. This is the
- * final step in creating decomposed normal forms D and KD.
- * @private
- * @param string $string a valid, decomposed UTF-8 string. Input is not validated.
- * @return string a UTF-8 string with combining characters sorted in canonical order
- */
- static function fastCombiningSort( $string ) {
- UtfNormal::loadData();
- $len = strlen( $string );
- $out = '';
- $combiners = array();
- $lastClass = -1;
- for ( $i = 0; $i < $len; $i++ ) {
- $c = $string[$i];
- $n = ord( $c );
- if ( $n >= 0x80 ) {
- if ( $n >= 0xf0 ) {
- $c = substr( $string, $i, 4 );
- $i += 3;
- } elseif ( $n >= 0xe0 ) {
- $c = substr( $string, $i, 3 );
- $i += 2;
- } elseif ( $n >= 0xc0 ) {
- $c = substr( $string, $i, 2 );
- $i++;
- }
- if ( isset( self::$utfCombiningClass[$c] ) ) {
- $lastClass = self::$utfCombiningClass[$c];
- if ( isset( $combiners[$lastClass] ) ) {
- $combiners[$lastClass] .= $c;
- } else {
- $combiners[$lastClass] = $c;
- }
- continue;
- }
- }
- if ( $lastClass ) {
- ksort( $combiners );
- $out .= implode( '', $combiners );
- $combiners = array();
- }
- $out .= $c;
- $lastClass = 0;
- }
- if ( $lastClass ) {
- ksort( $combiners );
- $out .= implode( '', $combiners );
- }
-
- return $out;
- }
-
- /**
- * 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.
- */
- static function fastCompose( $string ) {
- UtfNormal::loadData();
- $len = strlen( $string );
- $out = '';
- $lastClass = -1;
- $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++ ) {
- $c = $string[$i];
- $n = ord( $c );
- if ( $n < 0x80 ) {
- # No combining characters here...
- $out .= $startChar;
- $out .= $combining;
- $startChar = $c;
- $combining = '';
- $lastClass = 0;
- continue;
- } elseif ( $n >= 0xf0 ) {
- $c = substr( $string, $i, 4 );
- $i += 3;
- } elseif ( $n >= 0xe0 ) {
- $c = substr( $string, $i, 3 );
- $i += 2;
- } elseif ( $n >= 0xc0 ) {
- $c = substr( $string, $i, 2 );
- $i++;
- }
- $pair = $startChar . $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 ) &&
- $lastClass < $class &&
- $class > 0 &&
- isset( self::$utfCanonicalComp[$pair] )
- ) {
- $startChar = self::$utfCanonicalComp[$pair];
- $class = 0;
- } else {
- $combining .= $c;
- }
- $lastClass = $class;
- $lastHangul = 0;
- continue;
- }
- }
- # New start char
- if ( $lastClass == 0 ) {
- if ( isset( self::$utfCanonicalComp[$pair] ) ) {
- $startChar = self::$utfCanonicalComp[$pair];
- $lastHangul = 0;
- continue;
- }
- 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 &&
- $c <= UTF8_HANGUL_VEND &&
- $startChar >= UTF8_HANGUL_LBASE &&
- $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;
-
- $hangulPoint = UNICODE_HANGUL_FIRST +
- UNICODE_HANGUL_TCOUNT *
- ( 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 );
- $lastHangul = 0;
- continue;
- } 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 );
-
- # Increment the code point by $tIndex, without
- # the function overhead of decoding and recoding UTF-8
- #
- $tail = ord( $startChar[2] ) + $tIndex;
- if ( $tail > 0xbf ) {
- $tail -= 0x40;
- $mid = ord( $startChar[1] ) + 1;
- if ( $mid > 0xbf ) {
- $startChar[0] = chr( ord( $startChar[0] ) + 1 );
- $mid -= 0x40;
- }
- $startChar[1] = chr( $mid );
- }
- $startChar[2] = chr( $tail );
-
- # If there's another jamo char after this, *don't* try to merge it.
- $lastHangul = 1;
- continue;
- }
- }
- }
- $out .= $startChar;
- $out .= $combining;
- $startChar = $c;
- $combining = '';
- $lastClass = 0;
- $lastHangul = 0;
- }
- $out .= $startChar . $combining;
-
- return $out;
- }
-
- /**
- * This is just used for the benchmark, comparing how long it takes to
- * interate through a string without really doing anything of substance.
- * @param $string string
- * @return string
- */
- static function placebo( $string ) {
- $len = strlen( $string );
- $out = '';
- 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.
- *
- * @param string $string The string
- * @return String String with the character codes replaced.
- */
- private static function replaceForNativeNormalize( $string ) {
- $string = preg_replace(
- '/[\x00-\x08\x0b\x0c\x0e-\x1f]/',
- UTF8_REPLACEMENT,
- $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
deleted file mode 100644
index bd2bc4e4..00000000
--- a/includes/normal/UtfNormalBench.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-/**
- * Approximate benchmark for some basic operations.
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup UtfNormal
- */
-
-if ( PHP_SAPI != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
-if ( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
- dl( 'php_utfnormal.so' );
-}
-
-require_once 'UtfNormalDefines.php';
-require_once 'UtfNormalUtil.php';
-require_once 'UtfNormal.php';
-
-define( 'BENCH_CYCLES', 5 );
-
-$testfiles = array(
- 'testdata/washington.txt' => 'English text',
- 'testdata/berlin.txt' => 'German text',
- 'testdata/bulgakov.txt' => 'Russian text',
- 'testdata/tokyo.txt' => 'Japanese text',
- 'testdata/young.txt' => 'Korean text'
-);
-$normalizer = new UtfNormal;
-UtfNormal::loadData();
-foreach ( $testfiles as $file => $desc ) {
- benchmarkTest( $normalizer, $file, $desc );
-}
-
-# -------
-
-function benchmarkTest( &$u, $filename, $desc ) {
- print "Testing $filename ($desc)...\n";
- $data = file_get_contents( $filename );
- $forms = array(
-# 'placebo',
- 'cleanUp',
- 'toNFC',
-# 'toNFKC',
-# 'toNFD', 'toNFKD',
- 'NFC',
-# 'NFKC',
-# 'NFD', 'NFKD',
- array( 'fastDecompose', 'fastCombiningSort', 'fastCompose' ),
-# 'quickIsNFC', 'quickIsNFCVerify',
- );
-
- foreach ( $forms as $form ) {
- if ( is_array( $form ) ) {
- $str = $data;
- foreach ( $form as $step ) {
- $str = benchmarkForm( $u, $str, $step );
- }
- } else {
- benchmarkForm( $u, $data, $form );
- }
- }
-}
-
-function benchmarkForm( &$u, &$data, $form ) {
- #$start = microtime( true );
- for ( $i = 0; $i < BENCH_CYCLES; $i++ ) {
- $start = microtime( true );
- $out = $u->$form( $data, UtfNormal::$utfCanonicalDecomp );
- $deltas[] = ( microtime( true ) - $start );
- }
- #$delta = (microtime( true ) - $start) / BENCH_CYCLES;
- sort( $deltas );
- $delta = $deltas[0]; # Take shortest time
-
- $rate = intval( strlen( $data ) / $delta );
- $same = ( 0 == strcmp( $data, $out ) );
-
- printf( " %20s %6.1fms %12s bytes/s (%s)\n",
- $form,
- $delta * 1000.0,
- number_format( $rate ),
- ( $same ? 'no change' : 'changed' ) );
-
- return $out;
-}
diff --git a/includes/normal/UtfNormalData.inc b/includes/normal/UtfNormalData.inc
deleted file mode 100644
index 5755f6b9..00000000
--- a/includes/normal/UtfNormalData.inc
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-/**
- * This file was automatically generated -- do not edit!
- * Run UtfNormalGenerate.php to create this file again (make clean && make)
- *
- * @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:"𪘀";}' );
-UtfNormal::$utfCheckNFC = unserialize( 'a:1221:{s:2:"̀";s:1:"N";s:2:"́";s:1:"N";s:2:"̓";s:1:"N";s:2:"̈́";s:1:"N";s:2:"ʹ";s:1:"N";s:2:";";s:1:"N";s:2:"·";s:1:"N";s:3:"क़";s:1:"N";s:3:"ख़";s:1:"N";s:3:"ग़";s:1:"N";s:3:"ज़";s:1:"N";s:3:"ड़";s:1:"N";s:3:"ढ़";s:1:"N";s:3:"फ़";s:1:"N";s:3:"य़";s:1:"N";s:3:"ড়";s:1:"N";s:3:"ঢ়";s:1:"N";s:3:"য়";s:1:"N";s:3:"ਲ਼";s:1:"N";s:3:"ਸ਼";s:1:"N";s:3:"ਖ਼";s:1:"N";s:3:"ਗ਼";s:1:"N";s:3:"ਜ਼";s:1:"N";s:3:"ਫ਼";s:1:"N";s:3:"ଡ଼";s:1:"N";s:3:"ଢ଼";s:1:"N";s:3:"གྷ";s:1:"N";s:3:"ཌྷ";s:1:"N";s:3:"དྷ";s:1:"N";s:3:"བྷ";s:1:"N";s:3:"ཛྷ";s:1:"N";s:3:"ཀྵ";s:1:"N";s:3:"ཱི";s:1:"N";s:3:"ཱུ";s:1:"N";s:3:"ྲྀ";s:1:"N";s:3:"ླྀ";s:1:"N";s:3:"ཱྀ";s:1:"N";s:3:"ྒྷ";s:1:"N";s:3:"ྜྷ";s:1:"N";s:3:"ྡྷ";s:1:"N";s:3:"ྦྷ";s:1:"N";s:3:"ྫྷ";s:1:"N";s:3:"ྐྵ";s:1:"N";s:3:"ά";s:1:"N";s:3:"έ";s:1:"N";s:3:"ή";s:1:"N";s:3:"ί";s:1:"N";s:3:"ό";s:1:"N";s:3:"ύ";s:1:"N";s:3:"ώ";s:1:"N";s:3:"Ά";s:1:"N";s:3:"ι";s:1:"N";s:3:"Έ";s:1:"N";s:3:"Ή";s:1:"N";s:3:"ΐ";s:1:"N";s:3:"Ί";s:1:"N";s:3:"ΰ";s:1:"N";s:3:"Ύ";s:1:"N";s:3:"΅";s:1:"N";s:3:"`";s:1:"N";s:3:"Ό";s:1:"N";s:3:"Ώ";s:1:"N";s:3:"´";s:1:"N";s:3:" ";s:1:"N";s:3:" ";s:1:"N";s:3:"Ω";s:1:"N";s:3:"K";s:1:"N";s:3:"Å";s:1:"N";s:3:"〈";s:1:"N";s:3:"〉";s:1:"N";s:3:"⫝̸";s:1:"N";s:3:"豈";s:1:"N";s:3:"更";s:1:"N";s:3:"車";s:1:"N";s:3:"賈";s:1:"N";s:3:"滑";s:1:"N";s:3:"串";s:1:"N";s:3:"句";s:1:"N";s:3:"龜";s:1:"N";s:3:"龜";s:1:"N";s:3:"契";s:1:"N";s:3:"金";s:1:"N";s:3:"喇";s:1:"N";s:3:"奈";s:1:"N";s:3:"懶";s:1:"N";s:3:"癩";s:1:"N";s:3:"羅";s:1:"N";s:3:"蘿";s:1:"N";s:3:"螺";s:1:"N";s:3:"裸";s:1:"N";s:3:"邏";s:1:"N";s:3:"樂";s:1:"N";s:3:"洛";s:1:"N";s:3:"烙";s:1:"N";s:3:"珞";s:1:"N";s:3:"落";s:1:"N";s:3:"酪";s:1:"N";s:3:"駱";s:1:"N";s:3:"亂";s:1:"N";s:3:"卵";s:1:"N";s:3:"欄";s:1:"N";s:3:"爛";s:1:"N";s:3:"蘭";s:1:"N";s:3:"鸞";s:1:"N";s:3:"嵐";s:1:"N";s:3:"濫";s:1:"N";s:3:"藍";s:1:"N";s:3:"襤";s:1:"N";s:3:"拉";s:1:"N";s:3:"臘";s:1:"N";s:3:"蠟";s:1:"N";s:3:"廊";s:1:"N";s:3:"朗";s:1:"N";s:3:"浪";s:1:"N";s:3:"狼";s:1:"N";s:3:"郎";s:1:"N";s:3:"來";s:1:"N";s:3:"冷";s:1:"N";s:3:"勞";s:1:"N";s:3:"擄";s:1:"N";s:3:"櫓";s:1:"N";s:3:"爐";s:1:"N";s:3:"盧";s:1:"N";s:3:"老";s:1:"N";s:3:"蘆";s:1:"N";s:3:"虜";s:1:"N";s:3:"路";s:1:"N";s:3:"露";s:1:"N";s:3:"魯";s:1:"N";s:3:"鷺";s:1:"N";s:3:"碌";s:1:"N";s:3:"祿";s:1:"N";s:3:"綠";s:1:"N";s:3:"菉";s:1:"N";s:3:"錄";s:1:"N";s:3:"鹿";s:1:"N";s:3:"論";s:1:"N";s:3:"壟";s:1:"N";s:3:"弄";s:1:"N";s:3:"籠";s:1:"N";s:3:"聾";s:1:"N";s:3:"牢";s:1:"N";s:3:"磊";s:1:"N";s:3:"賂";s:1:"N";s:3:"雷";s:1:"N";s:3:"壘";s:1:"N";s:3:"屢";s:1:"N";s:3:"樓";s:1:"N";s:3:"淚";s:1:"N";s:3:"漏";s:1:"N";s:3:"累";s:1:"N";s:3:"縷";s:1:"N";s:3:"陋";s:1:"N";s:3:"勒";s:1:"N";s:3:"肋";s:1:"N";s:3:"凜";s:1:"N";s:3:"凌";s:1:"N";s:3:"稜";s:1:"N";s:3:"綾";s:1:"N";s:3:"菱";s:1:"N";s:3:"陵";s:1:"N";s:3:"讀";s:1:"N";s:3:"拏";s:1:"N";s:3:"樂";s:1:"N";s:3:"諾";s:1:"N";s:3:"丹";s:1:"N";s:3:"寧";s:1:"N";s:3:"怒";s:1:"N";s:3:"率";s:1:"N";s:3:"異";s:1:"N";s:3:"北";s:1:"N";s:3:"磻";s:1:"N";s:3:"便";s:1:"N";s:3:"復";s:1:"N";s:3:"不";s:1:"N";s:3:"泌";s:1:"N";s:3:"數";s:1:"N";s:3:"索";s:1:"N";s:3:"參";s:1:"N";s:3:"塞";s:1:"N";s:3:"省";s:1:"N";s:3:"葉";s:1:"N";s:3:"說";s:1:"N";s:3:"殺";s:1:"N";s:3:"辰";s:1:"N";s:3:"沈";s:1:"N";s:3:"拾";s:1:"N";s:3:"若";s:1:"N";s:3:"掠";s:1:"N";s:3:"略";s:1:"N";s:3:"亮";s:1:"N";s:3:"兩";s:1:"N";s:3:"凉";s:1:"N";s:3:"梁";s:1:"N";s:3:"糧";s:1:"N";s:3:"良";s:1:"N";s:3:"諒";s:1:"N";s:3:"量";s:1:"N";s:3:"勵";s:1:"N";s:3:"呂";s:1:"N";s:3:"女";s:1:"N";s:3:"廬";s:1:"N";s:3:"旅";s:1:"N";s:3:"濾";s:1:"N";s:3:"礪";s:1:"N";s:3:"閭";s:1:"N";s:3:"驪";s:1:"N";s:3:"麗";s:1:"N";s:3:"黎";s:1:"N";s:3:"力";s:1:"N";s:3:"曆";s:1:"N";s:3:"歷";s:1:"N";s:3:"轢";s:1:"N";s:3:"年";s:1:"N";s:3:"憐";s:1:"N";s:3:"戀";s:1:"N";s:3:"撚";s:1:"N";s:3:"漣";s:1:"N";s:3:"煉";s:1:"N";s:3:"璉";s:1:"N";s:3:"秊";s:1:"N";s:3:"練";s:1:"N";s:3:"聯";s:1:"N";s:3:"輦";s:1:"N";s:3:"蓮";s:1:"N";s:3:"連";s:1:"N";s:3:"鍊";s:1:"N";s:3:"列";s:1:"N";s:3:"劣";s:1:"N";s:3:"咽";s:1:"N";s:3:"烈";s:1:"N";s:3:"裂";s:1:"N";s:3:"說";s:1:"N";s:3:"廉";s:1:"N";s:3:"念";s:1:"N";s:3:"捻";s:1:"N";s:3:"殮";s:1:"N";s:3:"簾";s:1:"N";s:3:"獵";s:1:"N";s:3:"令";s:1:"N";s:3:"囹";s:1:"N";s:3:"寧";s:1:"N";s:3:"嶺";s:1:"N";s:3:"怜";s:1:"N";s:3:"玲";s:1:"N";s:3:"瑩";s:1:"N";s:3:"羚";s:1:"N";s:3:"聆";s:1:"N";s:3:"鈴";s:1:"N";s:3:"零";s:1:"N";s:3:"靈";s:1:"N";s:3:"領";s:1:"N";s:3:"例";s:1:"N";s:3:"禮";s:1:"N";s:3:"醴";s:1:"N";s:3:"隸";s:1:"N";s:3:"惡";s:1:"N";s:3:"了";s:1:"N";s:3:"僚";s:1:"N";s:3:"寮";s:1:"N";s:3:"尿";s:1:"N";s:3:"料";s:1:"N";s:3:"樂";s:1:"N";s:3:"燎";s:1:"N";s:3:"療";s:1:"N";s:3:"蓼";s:1:"N";s:3:"遼";s:1:"N";s:3:"龍";s:1:"N";s:3:"暈";s:1:"N";s:3:"阮";s:1:"N";s:3:"劉";s:1:"N";s:3:"杻";s:1:"N";s:3:"柳";s:1:"N";s:3:"流";s:1:"N";s:3:"溜";s:1:"N";s:3:"琉";s:1:"N";s:3:"留";s:1:"N";s:3:"硫";s:1:"N";s:3:"紐";s:1:"N";s:3:"類";s:1:"N";s:3:"六";s:1:"N";s:3:"戮";s:1:"N";s:3:"陸";s:1:"N";s:3:"倫";s:1:"N";s:3:"崙";s:1:"N";s:3:"淪";s:1:"N";s:3:"輪";s:1:"N";s:3:"律";s:1:"N";s:3:"慄";s:1:"N";s:3:"栗";s:1:"N";s:3:"率";s:1:"N";s:3:"隆";s:1:"N";s:3:"利";s:1:"N";s:3:"吏";s:1:"N";s:3:"履";s:1:"N";s:3:"易";s:1:"N";s:3:"李";s:1:"N";s:3:"梨";s:1:"N";s:3:"泥";s:1:"N";s:3:"理";s:1:"N";s:3:"痢";s:1:"N";s:3:"罹";s:1:"N";s:3:"裏";s:1:"N";s:3:"裡";s:1:"N";s:3:"里";s:1:"N";s:3:"離";s:1:"N";s:3:"匿";s:1:"N";s:3:"溺";s:1:"N";s:3:"吝";s:1:"N";s:3:"燐";s:1:"N";s:3:"璘";s:1:"N";s:3:"藺";s:1:"N";s:3:"隣";s:1:"N";s:3:"鱗";s:1:"N";s:3:"麟";s:1:"N";s:3:"林";s:1:"N";s:3:"淋";s:1:"N";s:3:"臨";s:1:"N";s:3:"立";s:1:"N";s:3:"笠";s:1:"N";s:3:"粒";s:1:"N";s:3:"狀";s:1:"N";s:3:"炙";s:1:"N";s:3:"識";s:1:"N";s:3:"什";s:1:"N";s:3:"茶";s:1:"N";s:3:"刺";s:1:"N";s:3:"切";s:1:"N";s:3:"度";s:1:"N";s:3:"拓";s:1:"N";s:3:"糖";s:1:"N";s:3:"宅";s:1:"N";s:3:"洞";s:1:"N";s:3:"暴";s:1:"N";s:3:"輻";s:1:"N";s:3:"行";s:1:"N";s:3:"降";s:1:"N";s:3:"見";s:1:"N";s:3:"廓";s:1:"N";s:3:"兀";s:1:"N";s:3:"嗀";s:1:"N";s:3:"塚";s:1:"N";s:3:"晴";s:1:"N";s:3:"凞";s:1:"N";s:3:"猪";s:1:"N";s:3:"益";s:1:"N";s:3:"礼";s:1:"N";s:3:"神";s:1:"N";s:3:"祥";s:1:"N";s:3:"福";s:1:"N";s:3:"靖";s:1:"N";s:3:"精";s:1:"N";s:3:"羽";s:1:"N";s:3:"蘒";s:1:"N";s:3:"諸";s:1:"N";s:3:"逸";s:1:"N";s:3:"都";s:1:"N";s:3:"飯";s:1:"N";s:3:"飼";s:1:"N";s:3:"館";s:1:"N";s:3:"鶴";s:1:"N";s:3:"侮";s:1:"N";s:3:"僧";s:1:"N";s:3:"免";s:1:"N";s:3:"勉";s:1:"N";s:3:"勤";s:1:"N";s:3:"卑";s:1:"N";s:3:"喝";s:1:"N";s:3:"嘆";s:1:"N";s:3:"器";s:1:"N";s:3:"塀";s:1:"N";s:3:"墨";s:1:"N";s:3:"層";s:1:"N";s:3:"屮";s:1:"N";s:3:"悔";s:1:"N";s:3:"慨";s:1:"N";s:3:"憎";s:1:"N";s:3:"懲";s:1:"N";s:3:"敏";s:1:"N";s:3:"既";s:1:"N";s:3:"暑";s:1:"N";s:3:"梅";s:1:"N";s:3:"海";s:1:"N";s:3:"渚";s:1:"N";s:3:"漢";s:1:"N";s:3:"煮";s:1:"N";s:3:"爫";s:1:"N";s:3:"琢";s:1:"N";s:3:"碑";s:1:"N";s:3:"社";s:1:"N";s:3:"祉";s:1:"N";s:3:"祈";s:1:"N";s:3:"祐";s:1:"N";s:3:"祖";s:1:"N";s:3:"祝";s:1:"N";s:3:"禍";s:1:"N";s:3:"禎";s:1:"N";s:3:"穀";s:1:"N";s:3:"突";s:1:"N";s:3:"節";s:1:"N";s:3:"練";s:1:"N";s:3:"縉";s:1:"N";s:3:"繁";s:1:"N";s:3:"署";s:1:"N";s:3:"者";s:1:"N";s:3:"臭";s:1:"N";s:3:"艹";s:1:"N";s:3:"艹";s:1:"N";s:3:"著";s:1:"N";s:3:"褐";s:1:"N";s:3:"視";s:1:"N";s:3:"謁";s:1:"N";s:3:"謹";s:1:"N";s:3:"賓";s:1:"N";s:3:"贈";s:1:"N";s:3:"辶";s:1:"N";s:3:"逸";s:1:"N";s:3:"難";s:1:"N";s:3:"響";s:1:"N";s:3:"頻";s:1:"N";s:3:"恵";s:1:"N";s:3:"𤋮";s:1:"N";s:3:"舘";s:1:"N";s:3:"並";s:1:"N";s:3:"况";s:1:"N";s:3:"全";s:1:"N";s:3:"侀";s:1:"N";s:3:"充";s:1:"N";s:3:"冀";s:1:"N";s:3:"勇";s:1:"N";s:3:"勺";s:1:"N";s:3:"喝";s:1:"N";s:3:"啕";s:1:"N";s:3:"喙";s:1:"N";s:3:"嗢";s:1:"N";s:3:"塚";s:1:"N";s:3:"墳";s:1:"N";s:3:"奄";s:1:"N";s:3:"奔";s:1:"N";s:3:"婢";s:1:"N";s:3:"嬨";s:1:"N";s:3:"廒";s:1:"N";s:3:"廙";s:1:"N";s:3:"彩";s:1:"N";s:3:"徭";s:1:"N";s:3:"惘";s:1:"N";s:3:"慎";s:1:"N";s:3:"愈";s:1:"N";s:3:"憎";s:1:"N";s:3:"慠";s:1:"N";s:3:"懲";s:1:"N";s:3:"戴";s:1:"N";s:3:"揄";s:1:"N";s:3:"搜";s:1:"N";s:3:"摒";s:1:"N";s:3:"敖";s:1:"N";s:3:"晴";s:1:"N";s:3:"朗";s:1:"N";s:3:"望";s:1:"N";s:3:"杖";s:1:"N";s:3:"歹";s:1:"N";s:3:"殺";s:1:"N";s:3:"流";s:1:"N";s:3:"滛";s:1:"N";s:3:"滋";s:1:"N";s:3:"漢";s:1:"N";s:3:"瀞";s:1:"N";s:3:"煮";s:1:"N";s:3:"瞧";s:1:"N";s:3:"爵";s:1:"N";s:3:"犯";s:1:"N";s:3:"猪";s:1:"N";s:3:"瑱";s:1:"N";s:3:"甆";s:1:"N";s:3:"画";s:1:"N";s:3:"瘝";s:1:"N";s:3:"瘟";s:1:"N";s:3:"益";s:1:"N";s:3:"盛";s:1:"N";s:3:"直";s:1:"N";s:3:"睊";s:1:"N";s:3:"着";s:1:"N";s:3:"磌";s:1:"N";s:3:"窱";s:1:"N";s:3:"節";s:1:"N";s:3:"类";s:1:"N";s:3:"絛";s:1:"N";s:3:"練";s:1:"N";s:3:"缾";s:1:"N";s:3:"者";s:1:"N";s:3:"荒";s:1:"N";s:3:"華";s:1:"N";s:3:"蝹";s:1:"N";s:3:"襁";s:1:"N";s:3:"覆";s:1:"N";s:3:"視";s:1:"N";s:3:"調";s:1:"N";s:3:"諸";s:1:"N";s:3:"請";s:1:"N";s:3:"謁";s:1:"N";s:3:"諾";s:1:"N";s:3:"諭";s:1:"N";s:3:"謹";s:1:"N";s:3:"變";s:1:"N";s:3:"贈";s:1:"N";s:3:"輸";s:1:"N";s:3:"遲";s:1:"N";s:3:"醙";s:1:"N";s:3:"鉶";s:1:"N";s:3:"陼";s:1:"N";s:3:"難";s:1:"N";s:3:"靖";s:1:"N";s:3:"韛";s:1:"N";s:3:"響";s:1:"N";s:3:"頋";s:1:"N";s:3:"頻";s:1:"N";s:3:"鬒";s:1:"N";s:3:"龜";s:1:"N";s:3:"𢡊";s:1:"N";s:3:"𢡄";s:1:"N";s:3:"𣏕";s:1:"N";s:3:"㮝";s:1:"N";s:3:"䀘";s:1:"N";s:3:"䀹";s:1:"N";s:3:"𥉉";s:1:"N";s:3:"𥳐";s:1:"N";s:3:"𧻓";s:1:"N";s:3:"齃";s:1:"N";s:3:"龎";s:1:"N";s:3:"יִ";s:1:"N";s:3:"ײַ";s:1:"N";s:3:"שׁ";s:1:"N";s:3:"שׂ";s:1:"N";s:3:"שּׁ";s:1:"N";s:3:"שּׂ";s:1:"N";s:3:"אַ";s:1:"N";s:3:"אָ";s:1:"N";s:3:"אּ";s:1:"N";s:3:"בּ";s:1:"N";s:3:"גּ";s:1:"N";s:3:"דּ";s:1:"N";s:3:"הּ";s:1:"N";s:3:"וּ";s:1:"N";s:3:"זּ";s:1:"N";s:3:"טּ";s:1:"N";s:3:"יּ";s:1:"N";s:3:"ךּ";s:1:"N";s:3:"כּ";s:1:"N";s:3:"לּ";s:1:"N";s:3:"מּ";s:1:"N";s:3:"נּ";s:1:"N";s:3:"סּ";s:1:"N";s:3:"ףּ";s:1:"N";s:3:"פּ";s:1:"N";s:3:"צּ";s:1:"N";s:3:"קּ";s:1:"N";s:3:"רּ";s:1:"N";s:3:"שּ";s:1:"N";s:3:"תּ";s:1:"N";s:3:"וֹ";s:1:"N";s:3:"בֿ";s:1:"N";s:3:"כֿ";s:1:"N";s:3:"פֿ";s:1:"N";s:4:"𝅗𝅥";s:1:"N";s:4:"𝅘𝅥";s:1:"N";s:4:"𝅘𝅥𝅮";s:1:"N";s:4:"𝅘𝅥𝅯";s:1:"N";s:4:"𝅘𝅥𝅰";s:1:"N";s:4:"𝅘𝅥𝅱";s:1:"N";s:4:"𝅘𝅥𝅲";s:1:"N";s:4:"𝆹𝅥";s:1:"N";s:4:"𝆺𝅥";s:1:"N";s:4:"𝆹𝅥𝅮";s:1:"N";s:4:"𝆺𝅥𝅮";s:1:"N";s:4:"𝆹𝅥𝅯";s:1:"N";s:4:"𝆺𝅥𝅯";s:1:"N";s:4:"丽";s:1:"N";s:4:"丸";s:1:"N";s:4:"乁";s:1:"N";s:4:"𠄢";s:1:"N";s:4:"你";s:1:"N";s:4:"侮";s:1:"N";s:4:"侻";s:1:"N";s:4:"倂";s:1:"N";s:4:"偺";s:1:"N";s:4:"備";s:1:"N";s:4:"僧";s:1:"N";s:4:"像";s:1:"N";s:4:"㒞";s:1:"N";s:4:"𠘺";s:1:"N";s:4:"免";s:1:"N";s:4:"兔";s:1:"N";s:4:"兤";s:1:"N";s:4:"具";s:1:"N";s:4:"𠔜";s:1:"N";s:4:"㒹";s:1:"N";s:4:"內";s:1:"N";s:4:"再";s:1:"N";s:4:"𠕋";s:1:"N";s:4:"冗";s:1:"N";s:4:"冤";s:1:"N";s:4:"仌";s:1:"N";s:4:"冬";s:1:"N";s:4:"况";s:1:"N";s:4:"𩇟";s:1:"N";s:4:"凵";s:1:"N";s:4:"刃";s:1:"N";s:4:"㓟";s:1:"N";s:4:"刻";s:1:"N";s:4:"剆";s:1:"N";s:4:"割";s:1:"N";s:4:"剷";s:1:"N";s:4:"㔕";s:1:"N";s:4:"勇";s:1:"N";s:4:"勉";s:1:"N";s:4:"勤";s:1:"N";s:4:"勺";s:1:"N";s:4:"包";s:1:"N";s:4:"匆";s:1:"N";s:4:"北";s:1:"N";s:4:"卉";s:1:"N";s:4:"卑";s:1:"N";s:4:"博";s:1:"N";s:4:"即";s:1:"N";s:4:"卽";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"𠨬";s:1:"N";s:4:"灰";s:1:"N";s:4:"及";s:1:"N";s:4:"叟";s:1:"N";s:4:"𠭣";s:1:"N";s:4:"叫";s:1:"N";s:4:"叱";s:1:"N";s:4:"吆";s:1:"N";s:4:"咞";s:1:"N";s:4:"吸";s:1:"N";s:4:"呈";s:1:"N";s:4:"周";s:1:"N";s:4:"咢";s:1:"N";s:4:"哶";s:1:"N";s:4:"唐";s:1:"N";s:4:"啓";s:1:"N";s:4:"啣";s:1:"N";s:4:"善";s:1:"N";s:4:"善";s:1:"N";s:4:"喙";s:1:"N";s:4:"喫";s:1:"N";s:4:"喳";s:1:"N";s:4:"嗂";s:1:"N";s:4:"圖";s:1:"N";s:4:"嘆";s:1:"N";s:4:"圗";s:1:"N";s:4:"噑";s:1:"N";s:4:"噴";s:1:"N";s:4:"切";s:1:"N";s:4:"壮";s:1:"N";s:4:"城";s:1:"N";s:4:"埴";s:1:"N";s:4:"堍";s:1:"N";s:4:"型";s:1:"N";s:4:"堲";s:1:"N";s:4:"報";s:1:"N";s:4:"墬";s:1:"N";s:4:"𡓤";s:1:"N";s:4:"売";s:1:"N";s:4:"壷";s:1:"N";s:4:"夆";s:1:"N";s:4:"多";s:1:"N";s:4:"夢";s:1:"N";s:4:"奢";s:1:"N";s:4:"𡚨";s:1:"N";s:4:"𡛪";s:1:"N";s:4:"姬";s:1:"N";s:4:"娛";s:1:"N";s:4:"娧";s:1:"N";s:4:"姘";s:1:"N";s:4:"婦";s:1:"N";s:4:"㛮";s:1:"N";s:4:"㛼";s:1:"N";s:4:"嬈";s:1:"N";s:4:"嬾";s:1:"N";s:4:"嬾";s:1:"N";s:4:"𡧈";s:1:"N";s:4:"寃";s:1:"N";s:4:"寘";s:1:"N";s:4:"寧";s:1:"N";s:4:"寳";s:1:"N";s:4:"𡬘";s:1:"N";s:4:"寿";s:1:"N";s:4:"将";s:1:"N";s:4:"当";s:1:"N";s:4:"尢";s:1:"N";s:4:"㞁";s:1:"N";s:4:"屠";s:1:"N";s:4:"屮";s:1:"N";s:4:"峀";s:1:"N";s:4:"岍";s:1:"N";s:4:"𡷤";s:1:"N";s:4:"嵃";s:1:"N";s:4:"𡷦";s:1:"N";s:4:"嵮";s:1:"N";s:4:"嵫";s:1:"N";s:4:"嵼";s:1:"N";s:4:"巡";s:1:"N";s:4:"巢";s:1:"N";s:4:"㠯";s:1:"N";s:4:"巽";s:1:"N";s:4:"帨";s:1:"N";s:4:"帽";s:1:"N";s:4:"幩";s:1:"N";s:4:"㡢";s:1:"N";s:4:"𢆃";s:1:"N";s:4:"㡼";s:1:"N";s:4:"庰";s:1:"N";s:4:"庳";s:1:"N";s:4:"庶";s:1:"N";s:4:"廊";s:1:"N";s:4:"𪎒";s:1:"N";s:4:"廾";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"舁";s:1:"N";s:4:"弢";s:1:"N";s:4:"弢";s:1:"N";s:4:"㣇";s:1:"N";s:4:"𣊸";s:1:"N";s:4:"𦇚";s:1:"N";s:4:"形";s:1:"N";s:4:"彫";s:1:"N";s:4:"㣣";s:1:"N";s:4:"徚";s:1:"N";s:4:"忍";s:1:"N";s:4:"志";s:1:"N";s:4:"忹";s:1:"N";s:4:"悁";s:1:"N";s:4:"㤺";s:1:"N";s:4:"㤜";s:1:"N";s:4:"悔";s:1:"N";s:4:"𢛔";s:1:"N";s:4:"惇";s:1:"N";s:4:"慈";s:1:"N";s:4:"慌";s:1:"N";s:4:"慎";s:1:"N";s:4:"慌";s:1:"N";s:4:"慺";s:1:"N";s:4:"憎";s:1:"N";s:4:"憲";s:1:"N";s:4:"憤";s:1:"N";s:4:"憯";s:1:"N";s:4:"懞";s:1:"N";s:4:"懲";s:1:"N";s:4:"懶";s:1:"N";s:4:"成";s:1:"N";s:4:"戛";s:1:"N";s:4:"扝";s:1:"N";s:4:"抱";s:1:"N";s:4:"拔";s:1:"N";s:4:"捐";s:1:"N";s:4:"𢬌";s:1:"N";s:4:"挽";s:1:"N";s:4:"拼";s:1:"N";s:4:"捨";s:1:"N";s:4:"掃";s:1:"N";s:4:"揤";s:1:"N";s:4:"𢯱";s:1:"N";s:4:"搢";s:1:"N";s:4:"揅";s:1:"N";s:4:"掩";s:1:"N";s:4:"㨮";s:1:"N";s:4:"摩";s:1:"N";s:4:"摾";s:1:"N";s:4:"撝";s:1:"N";s:4:"摷";s:1:"N";s:4:"㩬";s:1:"N";s:4:"敏";s:1:"N";s:4:"敬";s:1:"N";s:4:"𣀊";s:1:"N";s:4:"旣";s:1:"N";s:4:"書";s:1:"N";s:4:"晉";s:1:"N";s:4:"㬙";s:1:"N";s:4:"暑";s:1:"N";s:4:"㬈";s:1:"N";s:4:"㫤";s:1:"N";s:4:"冒";s:1:"N";s:4:"冕";s:1:"N";s:4:"最";s:1:"N";s:4:"暜";s:1:"N";s:4:"肭";s:1:"N";s:4:"䏙";s:1:"N";s:4:"朗";s:1:"N";s:4:"望";s:1:"N";s:4:"朡";s:1:"N";s:4:"杞";s:1:"N";s:4:"杓";s:1:"N";s:4:"𣏃";s:1:"N";s:4:"㭉";s:1:"N";s:4:"柺";s:1:"N";s:4:"枅";s:1:"N";s:4:"桒";s:1:"N";s:4:"梅";s:1:"N";s:4:"𣑭";s:1:"N";s:4:"梎";s:1:"N";s:4:"栟";s:1:"N";s:4:"椔";s:1:"N";s:4:"㮝";s:1:"N";s:4:"楂";s:1:"N";s:4:"榣";s:1:"N";s:4:"槪";s:1:"N";s:4:"檨";s:1:"N";s:4:"𣚣";s:1:"N";s:4:"櫛";s:1:"N";s:4:"㰘";s:1:"N";s:4:"次";s:1:"N";s:4:"𣢧";s:1:"N";s:4:"歔";s:1:"N";s:4:"㱎";s:1:"N";s:4:"歲";s:1:"N";s:4:"殟";s:1:"N";s:4:"殺";s:1:"N";s:4:"殻";s:1:"N";s:4:"𣪍";s:1:"N";s:4:"𡴋";s:1:"N";s:4:"𣫺";s:1:"N";s:4:"汎";s:1:"N";s:4:"𣲼";s:1:"N";s:4:"沿";s:1:"N";s:4:"泍";s:1:"N";s:4:"汧";s:1:"N";s:4:"洖";s:1:"N";s:4:"派";s:1:"N";s:4:"海";s:1:"N";s:4:"流";s:1:"N";s:4:"浩";s:1:"N";s:4:"浸";s:1:"N";s:4:"涅";s:1:"N";s:4:"𣴞";s:1:"N";s:4:"洴";s:1:"N";s:4:"港";s:1:"N";s:4:"湮";s:1:"N";s:4:"㴳";s:1:"N";s:4:"滋";s:1:"N";s:4:"滇";s:1:"N";s:4:"𣻑";s:1:"N";s:4:"淹";s:1:"N";s:4:"潮";s:1:"N";s:4:"𣽞";s:1:"N";s:4:"𣾎";s:1:"N";s:4:"濆";s:1:"N";s:4:"瀹";s:1:"N";s:4:"瀞";s:1:"N";s:4:"瀛";s:1:"N";s:4:"㶖";s:1:"N";s:4:"灊";s:1:"N";s:4:"災";s:1:"N";s:4:"灷";s:1:"N";s:4:"炭";s:1:"N";s:4:"𠔥";s:1:"N";s:4:"煅";s:1:"N";s:4:"𤉣";s:1:"N";s:4:"熜";s:1:"N";s:4:"𤎫";s:1:"N";s:4:"爨";s:1:"N";s:4:"爵";s:1:"N";s:4:"牐";s:1:"N";s:4:"𤘈";s:1:"N";s:4:"犀";s:1:"N";s:4:"犕";s:1:"N";s:4:"𤜵";s:1:"N";s:4:"𤠔";s:1:"N";s:4:"獺";s:1:"N";s:4:"王";s:1:"N";s:4:"㺬";s:1:"N";s:4:"玥";s:1:"N";s:4:"㺸";s:1:"N";s:4:"㺸";s:1:"N";s:4:"瑇";s:1:"N";s:4:"瑜";s:1:"N";s:4:"瑱";s:1:"N";s:4:"璅";s:1:"N";s:4:"瓊";s:1:"N";s:4:"㼛";s:1:"N";s:4:"甤";s:1:"N";s:4:"𤰶";s:1:"N";s:4:"甾";s:1:"N";s:4:"𤲒";s:1:"N";s:4:"異";s:1:"N";s:4:"𢆟";s:1:"N";s:4:"瘐";s:1:"N";s:4:"𤾡";s:1:"N";s:4:"𤾸";s:1:"N";s:4:"𥁄";s:1:"N";s:4:"㿼";s:1:"N";s:4:"䀈";s:1:"N";s:4:"直";s:1:"N";s:4:"𥃳";s:1:"N";s:4:"𥃲";s:1:"N";s:4:"𥄙";s:1:"N";s:4:"𥄳";s:1:"N";s:4:"眞";s:1:"N";s:4:"真";s:1:"N";s:4:"真";s:1:"N";s:4:"睊";s:1:"N";s:4:"䀹";s:1:"N";s:4:"瞋";s:1:"N";s:4:"䁆";s:1:"N";s:4:"䂖";s:1:"N";s:4:"𥐝";s:1:"N";s:4:"硎";s:1:"N";s:4:"碌";s:1:"N";s:4:"磌";s:1:"N";s:4:"䃣";s:1:"N";s:4:"𥘦";s:1:"N";s:4:"祖";s:1:"N";s:4:"𥚚";s:1:"N";s:4:"𥛅";s:1:"N";s:4:"福";s:1:"N";s:4:"秫";s:1:"N";s:4:"䄯";s:1:"N";s:4:"穀";s:1:"N";s:4:"穊";s:1:"N";s:4:"穏";s:1:"N";s:4:"𥥼";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"竮";s:1:"N";s:4:"䈂";s:1:"N";s:4:"𥮫";s:1:"N";s:4:"篆";s:1:"N";s:4:"築";s:1:"N";s:4:"䈧";s:1:"N";s:4:"𥲀";s:1:"N";s:4:"糒";s:1:"N";s:4:"䊠";s:1:"N";s:4:"糨";s:1:"N";s:4:"糣";s:1:"N";s:4:"紀";s:1:"N";s:4:"𥾆";s:1:"N";s:4:"絣";s:1:"N";s:4:"䌁";s:1:"N";s:4:"緇";s:1:"N";s:4:"縂";s:1:"N";s:4:"繅";s:1:"N";s:4:"䌴";s:1:"N";s:4:"𦈨";s:1:"N";s:4:"𦉇";s:1:"N";s:4:"䍙";s:1:"N";s:4:"𦋙";s:1:"N";s:4:"罺";s:1:"N";s:4:"𦌾";s:1:"N";s:4:"羕";s:1:"N";s:4:"翺";s:1:"N";s:4:"者";s:1:"N";s:4:"𦓚";s:1:"N";s:4:"𦔣";s:1:"N";s:4:"聠";s:1:"N";s:4:"𦖨";s:1:"N";s:4:"聰";s:1:"N";s:4:"𣍟";s:1:"N";s:4:"䏕";s:1:"N";s:4:"育";s:1:"N";s:4:"脃";s:1:"N";s:4:"䐋";s:1:"N";s:4:"脾";s:1:"N";s:4:"媵";s:1:"N";s:4:"𦞧";s:1:"N";s:4:"𦞵";s:1:"N";s:4:"𣎓";s:1:"N";s:4:"𣎜";s:1:"N";s:4:"舁";s:1:"N";s:4:"舄";s:1:"N";s:4:"辞";s:1:"N";s:4:"䑫";s:1:"N";s:4:"芑";s:1:"N";s:4:"芋";s:1:"N";s:4:"芝";s:1:"N";s:4:"劳";s:1:"N";s:4:"花";s:1:"N";s:4:"芳";s:1:"N";s:4:"芽";s:1:"N";s:4:"苦";s:1:"N";s:4:"𦬼";s:1:"N";s:4:"若";s:1:"N";s:4:"茝";s:1:"N";s:4:"荣";s:1:"N";s:4:"莭";s:1:"N";s:4:"茣";s:1:"N";s:4:"莽";s:1:"N";s:4:"菧";s:1:"N";s:4:"著";s:1:"N";s:4:"荓";s:1:"N";s:4:"菊";s:1:"N";s:4:"菌";s:1:"N";s:4:"菜";s:1:"N";s:4:"𦰶";s:1:"N";s:4:"𦵫";s:1:"N";s:4:"𦳕";s:1:"N";s:4:"䔫";s:1:"N";s:4:"蓱";s:1:"N";s:4:"蓳";s:1:"N";s:4:"蔖";s:1:"N";s:4:"𧏊";s:1:"N";s:4:"蕤";s:1:"N";s:4:"𦼬";s:1:"N";s:4:"䕝";s:1:"N";s:4:"䕡";s:1:"N";s:4:"𦾱";s:1:"N";s:4:"𧃒";s:1:"N";s:4:"䕫";s:1:"N";s:4:"虐";s:1:"N";s:4:"虜";s:1:"N";s:4:"虧";s:1:"N";s:4:"虩";s:1:"N";s:4:"蚩";s:1:"N";s:4:"蚈";s:1:"N";s:4:"蜎";s:1:"N";s:4:"蛢";s:1:"N";s:4:"蝹";s:1:"N";s:4:"蜨";s:1:"N";s:4:"蝫";s:1:"N";s:4:"螆";s:1:"N";s:4:"䗗";s:1:"N";s:4:"蟡";s:1:"N";s:4:"蠁";s:1:"N";s:4:"䗹";s:1:"N";s:4:"衠";s:1:"N";s:4:"衣";s:1:"N";s:4:"𧙧";s:1:"N";s:4:"裗";s:1:"N";s:4:"裞";s:1:"N";s:4:"䘵";s:1:"N";s:4:"裺";s:1:"N";s:4:"㒻";s:1:"N";s:4:"𧢮";s:1:"N";s:4:"𧥦";s:1:"N";s:4:"䚾";s:1:"N";s:4:"䛇";s:1:"N";s:4:"誠";s:1:"N";s:4:"諭";s:1:"N";s:4:"變";s:1:"N";s:4:"豕";s:1:"N";s:4:"𧲨";s:1:"N";s:4:"貫";s:1:"N";s:4:"賁";s:1:"N";s:4:"贛";s:1:"N";s:4:"起";s:1:"N";s:4:"𧼯";s:1:"N";s:4:"𠠄";s:1:"N";s:4:"跋";s:1:"N";s:4:"趼";s:1:"N";s:4:"跰";s:1:"N";s:4:"𠣞";s:1:"N";s:4:"軔";s:1:"N";s:4:"輸";s:1:"N";s:4:"𨗒";s:1:"N";s:4:"𨗭";s:1:"N";s:4:"邔";s:1:"N";s:4:"郱";s:1:"N";s:4:"鄑";s:1:"N";s:4:"𨜮";s:1:"N";s:4:"鄛";s:1:"N";s:4:"鈸";s:1:"N";s:4:"鋗";s:1:"N";s:4:"鋘";s:1:"N";s:4:"鉼";s:1:"N";s:4:"鏹";s:1:"N";s:4:"鐕";s:1:"N";s:4:"𨯺";s:1:"N";s:4:"開";s:1:"N";s:4:"䦕";s:1:"N";s:4:"閷";s:1:"N";s:4:"𨵷";s:1:"N";s:4:"䧦";s:1:"N";s:4:"雃";s:1:"N";s:4:"嶲";s:1:"N";s:4:"霣";s:1:"N";s:4:"𩅅";s:1:"N";s:4:"𩈚";s:1:"N";s:4:"䩮";s:1:"N";s:4:"䩶";s:1:"N";s:4:"韠";s:1:"N";s:4:"𩐊";s:1:"N";s:4:"䪲";s:1:"N";s:4:"𩒖";s:1:"N";s:4:"頋";s:1:"N";s:4:"頋";s:1:"N";s:4:"頩";s:1:"N";s:4:"𩖶";s:1:"N";s:4:"飢";s:1:"N";s:4:"䬳";s:1:"N";s:4:"餩";s:1:"N";s:4:"馧";s:1:"N";s:4:"駂";s:1:"N";s:4:"駾";s:1:"N";s:4:"䯎";s:1:"N";s:4:"𩬰";s:1:"N";s:4:"鬒";s:1:"N";s:4:"鱀";s:1:"N";s:4:"鳽";s:1:"N";s:4:"䳎";s:1:"N";s:4:"䳭";s:1:"N";s:4:"鵧";s:1:"N";s:4:"𪃎";s:1:"N";s:4:"䳸";s:1:"N";s:4:"𪄅";s:1:"N";s:4:"𪈎";s:1:"N";s:4:"𪊑";s:1:"N";s:4:"麻";s:1:"N";s:4:"䵖";s:1:"N";s:4:"黹";s:1:"N";s:4:"黾";s:1:"N";s:4:"鼅";s:1:"N";s:4:"鼏";s:1:"N";s:4:"鼖";s:1:"N";s:4:"鼻";s:1:"N";s:4:"𪘀";s:1:"N";s:2:"̀";s:1:"M";s:2:"́";s:1:"M";s:2:"̂";s:1:"M";s:2:"̃";s:1:"M";s:2:"̄";s:1:"M";s:2:"̆";s:1:"M";s:2:"̇";s:1:"M";s:2:"̈";s:1:"M";s:2:"̉";s:1:"M";s:2:"̊";s:1:"M";s:2:"̋";s:1:"M";s:2:"̌";s:1:"M";s:2:"̏";s:1:"M";s:2:"̑";s:1:"M";s:2:"̓";s:1:"M";s:2:"̔";s:1:"M";s:2:"̛";s:1:"M";s:2:"̣";s:1:"M";s:2:"̤";s:1:"M";s:2:"̥";s:1:"M";s:2:"̦";s:1:"M";s:2:"̧";s:1:"M";s:2:"̨";s:1:"M";s:2:"̭";s:1:"M";s:2:"̮";s:1:"M";s:2:"̰";s:1:"M";s:2:"̱";s:1:"M";s:2:"̸";s:1:"M";s:2:"͂";s:1:"M";s:2:"ͅ";s:1:"M";s:2:"ٓ";s:1:"M";s:2:"ٔ";s:1:"M";s:2:"ٕ";s:1:"M";s:3:"़";s:1:"M";s:3:"া";s:1:"M";s:3:"ৗ";s:1:"M";s:3:"ା";s:1:"M";s:3:"ୖ";s:1:"M";s:3:"ୗ";s:1:"M";s:3:"ா";s:1:"M";s:3:"ௗ";s:1:"M";s:3:"ౖ";s:1:"M";s:3:"ೂ";s:1:"M";s:3:"ೕ";s:1:"M";s:3:"ೖ";s:1:"M";s:3:"ാ";s:1:"M";s:3:"ൗ";s:1:"M";s:3:"්";s:1:"M";s:3:"ා";s:1:"M";s:3:"ෟ";s:1:"M";s:3:"ီ";s:1:"M";s:3:"ᅡ";s:1:"M";s:3:"ᅢ";s:1:"M";s:3:"ᅣ";s:1:"M";s:3:"ᅤ";s:1:"M";s:3:"ᅥ";s:1:"M";s:3:"ᅦ";s:1:"M";s:3:"ᅧ";s:1:"M";s:3:"ᅨ";s:1:"M";s:3:"ᅩ";s:1:"M";s:3:"ᅪ";s:1:"M";s:3:"ᅫ";s:1:"M";s:3:"ᅬ";s:1:"M";s:3:"ᅭ";s:1:"M";s:3:"ᅮ";s:1:"M";s:3:"ᅯ";s:1:"M";s:3:"ᅰ";s:1:"M";s:3:"ᅱ";s:1:"M";s:3:"ᅲ";s:1:"M";s:3:"ᅳ";s:1:"M";s:3:"ᅴ";s:1:"M";s:3:"ᅵ";s:1:"M";s:3:"ᆨ";s:1:"M";s:3:"ᆩ";s:1:"M";s:3:"ᆪ";s:1:"M";s:3:"ᆫ";s:1:"M";s:3:"ᆬ";s:1:"M";s:3:"ᆭ";s:1:"M";s:3:"ᆮ";s:1:"M";s:3:"ᆯ";s:1:"M";s:3:"ᆰ";s:1:"M";s:3:"ᆱ";s:1:"M";s:3:"ᆲ";s:1:"M";s:3:"ᆳ";s:1:"M";s:3:"ᆴ";s:1:"M";s:3:"ᆵ";s:1:"M";s:3:"ᆶ";s:1:"M";s:3:"ᆷ";s:1:"M";s:3:"ᆸ";s:1:"M";s:3:"ᆹ";s:1:"M";s:3:"ᆺ";s:1:"M";s:3:"ᆻ";s:1:"M";s:3:"ᆼ";s:1:"M";s:3:"ᆽ";s:1:"M";s:3:"ᆾ";s:1:"M";s:3:"ᆿ";s:1:"M";s:3:"ᇀ";s:1:"M";s:3:"ᇁ";s:1:"M";s:3:"ᇂ";s:1:"M";s:3:"ᬵ";s:1:"M";s:3:"゙";s:1:"M";s:3:"゚";s:1:"M";s:4:"𑂺";s:1:"M";}' );
-
diff --git a/includes/normal/UtfNormalDataK.inc b/includes/normal/UtfNormalDataK.inc
deleted file mode 100644
index dde3effb..00000000
--- a/includes/normal/UtfNormalDataK.inc
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-/**
- * This file was automatically generated -- do not edit!
- * Run UtfNormalGenerate.php to create this file again (make clean && make)
- *
- * @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/UtfNormalGenerate.php b/includes/normal/UtfNormalGenerate.php
deleted file mode 100644
index f57aa6cc..00000000
--- a/includes/normal/UtfNormalGenerate.php
+++ /dev/null
@@ -1,250 +0,0 @@
-<?php
-/**
- * This script generates UniNormalData.inc from the Unicode Character Database
- * and supplementary files.
- *
- * Copyright (C) 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @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( "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 );
-}
-print "Initializing normalization quick check tables...\n";
-$checkNFC = array();
-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 )
- ) {
- list( $junk, $first, $last, $prop, $value ) = $matches;
- #print "$first $last $prop $value\n";
- if ( !$last ) {
- $last = $first;
- }
-
- $lastInDecimal = hexdec( $last );
- for ( $i = hexdec( $first ); $i <= $lastInDecimal; $i++ ) {
- $char = codepointToUtf8( $i );
- $checkNFC[$char] = $value;
- }
- }
-}
-fclose( $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 );
-}
-$exclude = array();
-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 );
-
-$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 );
-}
-
-$compatibilityDecomp = array();
-$canonicalDecomp = array();
-$canonicalComp = array();
-$combiningClass = array();
-$total = 0;
-$compat = 0;
-$canon = 0;
-
-print "Reading character definitions...\n";
-while ( false !== ( $line = fgets( $in ) ) ) {
- $columns = explode( ';', $line );
- $codepoint = $columns[0];
- $name = $columns[1];
- $canonicalCombiningClass = $columns[3];
- $decompositionMapping = $columns[5];
-
- $source = codepointToUtf8( hexdec( $codepoint ) );
-
- if ( $canonicalCombiningClass != 0 ) {
- $combiningClass[$source] = intval( $canonicalCombiningClass );
- }
-
- if ( $decompositionMapping === '' ) continue;
- if ( preg_match( '/^<(.+)> (.*)$/', $decompositionMapping, $matches ) ) {
- # Compatibility decomposition
- $canonical = false;
- $decompositionMapping = $matches[2];
- $compat++;
- } else {
- $canonical = true;
- $canon++;
- }
- $total++;
- $dest = hexSequenceToUtf8( $decompositionMapping );
-
- $compatibilityDecomp[$source] = $dest;
- if ( $canonical ) {
- $canonicalDecomp[$source] = $dest;
- if ( empty( $exclude[$source] ) ) {
- $canonicalComp[$dest] = $source;
- }
- }
- #print "$codepoint | $canonicalCombiningClasses | $decompositionMapping\n";
-}
-fclose( $in );
-
-print "Recursively expanding canonical mappings...\n";
-$changed = 42;
-$pass = 1;
-while ( $changed > 0 ) {
- print "pass $pass\n";
- $changed = 0;
- foreach ( $canonicalDecomp as $source => $dest ) {
- $newDest = preg_replace_callback(
- '/([\xc0-\xff][\x80-\xbf]+)/',
- 'callbackCanonical',
- $dest );
- if ( $newDest === $dest ) continue;
- $changed++;
- $canonicalDecomp[$source] = $newDest;
- }
- $pass++;
-}
-
-print "Recursively expanding compatibility mappings...\n";
-$changed = 42;
-$pass = 1;
-while ( $changed > 0 ) {
- print "pass $pass\n";
- $changed = 0;
- foreach ( $compatibilityDecomp as $source => $dest ) {
- $newDest = preg_replace_callback(
- '/([\xc0-\xff][\x80-\xbf]+)/',
- 'callbackCompat',
- $dest );
- if ( $newDest === $dest ) continue;
- $changed++;
- $compatibilityDecomp[$source] = $newDest;
- }
- $pass++;
-}
-
-print "$total decomposition mappings ($canon canonical, $compat compatibility)\n";
-
-$out = fopen( "UtfNormalData.inc", "wt" );
-if ( $out ) {
- $serCombining = escapeSingleString( serialize( $combiningClass ) );
- $serComp = escapeSingleString( serialize( $canonicalComp ) );
- $serCanon = escapeSingleString( serialize( $canonicalDecomp ) );
- $serCheckNFC = escapeSingleString( serialize( $checkNFC ) );
- $outdata = "<" . "?php
-/**
- * This file was automatically generated -- do not edit!
- * Run UtfNormalGenerate.php to create this file again (make clean && make)
- *
- * @file
- */
-// @codingStandardsIgnoreFile
-
-UtfNormal::\$utfCombiningClass = unserialize( '$serCombining' );
-UtfNormal::\$utfCanonicalComp = unserialize( '$serComp' );
-UtfNormal::\$utfCanonicalDecomp = unserialize( '$serCanon' );
-UtfNormal::\$utfCheckNFC = unserialize( '$serCheckNFC' );
-\n";
- fputs( $out, $outdata );
- fclose( $out );
- print "Wrote out UtfNormalData.inc\n";
-} else {
- print "Can't create file UtfNormalData.inc\n";
- exit( -1 );
-}
-
-$out = fopen( "UtfNormalDataK.inc", "wt" );
-if ( $out ) {
- $serCompat = escapeSingleString( serialize( $compatibilityDecomp ) );
- $outdata = "<" . "?php
-/**
- * This file was automatically generated -- do not edit!
- * Run UtfNormalGenerate.php to create this file again (make clean && make)
- *
- * @file
- */
-// @codingStandardsIgnoreFile
-
-UtfNormal::\$utfCompatibilityDecomp = unserialize( '$serCompat' );
-\n";
- fputs( $out, $outdata );
- fclose( $out );
- print "Wrote out UtfNormalDataK.inc\n";
- exit( 0 );
-} else {
- print "Can't create file UtfNormalDataK.inc\n";
- exit( -1 );
-}
-
-# ---------------
-
-function callbackCanonical( $matches ) {
- // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
- global $canonicalDecomp;
- // @codingStandardsIgnoreEnd
-
- if ( isset( $canonicalDecomp[$matches[1]] ) ) {
- return $canonicalDecomp[$matches[1]];
- }
-
- return $matches[1];
-}
-
-function callbackCompat( $matches ) {
- // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
- global $compatibilityDecomp;
- // @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
deleted file mode 100644
index f133e4d5..00000000
--- a/includes/normal/UtfNormalMemStress.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/**
- * Approximate benchmark for some basic operations.
- * Runs large chunks of text through cleanup with a lowish memory limit,
- * to test regression on mem usage (bug 28146)
- *
- * Copyright © 2004-2011 Brion Vibber <brion@wikimedia.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
- * 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" );
-}
-
-if ( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
- dl( 'php_utfnormal.so' );
-}
-
-require_once 'UtfNormalDefines.php';
-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 );
-
-$testfiles = array(
- 'testdata/washington.txt' => 'English text',
- 'testdata/berlin.txt' => 'German text',
- 'testdata/bulgakov.txt' => 'Russian text',
- 'testdata/tokyo.txt' => 'Japanese text',
- 'testdata/young.txt' => 'Korean text'
-);
-$normalizer = new UtfNormal;
-UtfNormal::loadData();
-foreach ( $testfiles as $file => $desc ) {
- benchmarkTest( $normalizer, $file, $desc );
-}
-
-# -------
-
-function benchmarkTest( &$u, $filename, $desc ) {
- print "Testing $filename ($desc)...\n";
- $data = file_get_contents( $filename );
- $all = $data;
- while ( strlen( $all ) < BIGSIZE ) {
- $all .= $all;
- }
- $data = $all;
- echo "Data is " . strlen( $data ) . " bytes.\n";
- $forms = array(
- 'quickIsNFCVerify',
- 'cleanUp',
- );
-
- foreach ( $forms as $form ) {
- if ( is_array( $form ) ) {
- $str = $data;
- foreach ( $form as $step ) {
- $str = benchmarkForm( $u, $str, $step );
- }
- } else {
- benchmarkForm( $u, $data, $form );
- }
- }
-}
-
-function benchmarkForm( &$u, &$data, $form ) {
- #$start = microtime( true );
- for ( $i = 0; $i < BENCH_CYCLES; $i++ ) {
- $start = microtime( true );
- $out = $u->$form( $data, UtfNormal::$utfCanonicalDecomp );
- $deltas[] = ( microtime( true ) - $start );
- }
- #$delta = (microtime( true ) - $start) / BENCH_CYCLES;
- sort( $deltas );
- $delta = $deltas[0]; # Take shortest time
-
- $rate = intval( strlen( $data ) / $delta );
- $same = ( 0 == strcmp( $data, $out ) );
-
- printf( " %20s %6.1fms %12s bytes/s (%s)\n",
- $form,
- $delta * 1000.0,
- number_format( $rate ),
- ( $same ? 'no change' : 'changed' ) );
-
- return $out;
-}
diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php
deleted file mode 100644
index 10cd0f0c..00000000
--- a/includes/normal/UtfNormalTest.php
+++ /dev/null
@@ -1,257 +0,0 @@
-<?php
-/**
- * Implements the conformance test at:
- * http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup UtfNormal
- */
-
-if ( PHP_SAPI != 'cli' ) {
- die( "Run me from the command line please.\n" );
-}
-
-$verbose = true;
-#define( 'PRETTY_UTF8', true );
-
-if ( defined( 'PRETTY_UTF8' ) ) {
- function pretty( $string ) {
- return strtoupper( bin2hex( $string ) );
- }
-} else {
- /**
- * @ignore
- * @param string $string
- * @return string
- */
- function pretty( $string ) {
- return strtoupper( utf8ToHexSequence( $string ) );
- }
-}
-
-if ( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
- dl( 'php_utfnormal.so' );
-}
-
-require_once 'UtfNormalDefines.php';
-require_once 'UtfNormalUtil.php';
-require_once 'UtfNormal.php';
-
-$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 );
-}
-
-$normalizer = new UtfNormal;
-
-$total = 0;
-$success = 0;
-$failure = 0;
-$ok = true;
-$testedChars = array();
-
-while ( false !== ( $line = fgets( $in ) ) ) {
- list( $data, $comment ) = explode( '#', $line );
- if ( $data === '' ) continue;
- $matches = array();
- if ( preg_match( '/@Part([\d])/', $data, $matches ) ) {
- if ( $matches[1] > 0 ) {
- $ok = reportResults( $total, $success, $failure ) && $ok;
- }
- print "Part {$matches[1]}: $comment";
- continue;
- }
-
- $columns = array_map( "hexSequenceToUtf8", explode( ";", $data ) );
- array_unshift( $columns, '' );
-
- $testedChars[$columns[1]] = true;
- $total++;
- if ( testNormals( $normalizer, $columns, $comment, $verbose ) ) {
- $success++;
- } else {
- $failure++;
- # print "FAILED: $comment";
- }
- if ( $total % 100 == 0 ) print "$total ";
-}
-fclose( $in );
-
-$ok = reportResults( $total, $success, $failure ) && $ok;
-
-$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 );
-}
-print "Now testing invariants...\n";
-
-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 ) {
- # 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] ) ) {
- $total++;
- if ( testInvariant( $normalizer, $char, $desc, $verbose ) ) {
- $success++;
- } else {
- $failure++;
- }
- if ( $total % 100 == 0 ) print "$total ";
- }
-}
-fclose( $in );
-
-$ok = reportResults( $total, $success, $failure ) && $ok;
-
-if ( $ok ) {
- print "TEST SUCCEEDED!\n";
- exit( 0 );
-} else {
- print "TEST FAILED!\n";
- exit( -1 );
-}
-
-## ------
-
-function reportResults( &$total, &$success, &$failure ) {
- $percSucc = intval( $success * 100 / $total );
- $percFail = intval( $failure * 100 / $total );
- print "\n";
- print "$success tests successful ($percSucc%)\n";
- print "$failure tests failed ($percFail%)\n\n";
- $ok = ( $success > 0 && $failure == 0 );
- $total = 0;
- $success = 0;
- $failure = 0;
-
- return $ok;
-}
-
-function testNormals( &$u, $c, $comment, $verbose, $reportFailure = false ) {
- $result = testNFC( $u, $c, $comment, $reportFailure );
- $result = testNFD( $u, $c, $comment, $reportFailure ) && $result;
- $result = testNFKC( $u, $c, $comment, $reportFailure ) && $result;
- $result = testNFKD( $u, $c, $comment, $reportFailure ) && $result;
- $result = testCleanUp( $u, $c, $comment, $reportFailure ) && $result;
-
- 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 ) {
- $aa = pretty( $a );
- $bb = pretty( $b );
- $ok = $result ? "succeed" : " failed";
- $eq = $result ? "==" : "!=";
- print " $ok $form c$col '$aa' $eq '$bb'\n";
- }
-
- return $result;
-}
-
-function testNFC( &$u, $c, $comment, $verbose ) {
- $result = verbosify( $c[2], $u->toNFC( $c[1] ), 1, 'NFC', $verbose );
- $result = verbosify( $c[2], $u->toNFC( $c[2] ), 2, 'NFC', $verbose ) && $result;
- $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;
-}
-
-function testCleanUp( &$u, $c, $comment, $verbose ) {
- $x = $c[1];
- $result = verbosify( $c[2], $u->cleanUp( $x ), 1, 'cleanUp', $verbose );
- $x = $c[2];
- $result = verbosify( $c[2], $u->cleanUp( $x ), 2, 'cleanUp', $verbose ) && $result;
- $x = $c[3];
- $result = verbosify( $c[2], $u->cleanUp( $x ), 3, 'cleanUp', $verbose ) && $result;
- $x = $c[4];
- $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;
-}
-
-function testNFD( &$u, $c, $comment, $verbose ) {
- $result = verbosify( $c[3], $u->toNFD( $c[1] ), 1, 'NFD', $verbose );
- $result = verbosify( $c[3], $u->toNFD( $c[2] ), 2, 'NFD', $verbose ) && $result;
- $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;
-}
-
-function testNFKC( &$u, $c, $comment, $verbose ) {
- $result = verbosify( $c[4], $u->toNFKC( $c[1] ), 1, 'NFKC', $verbose );
- $result = verbosify( $c[4], $u->toNFKC( $c[2] ), 2, 'NFKC', $verbose ) && $result;
- $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;
-}
-
-function testNFKD( &$u, $c, $comment, $verbose ) {
- $result = verbosify( $c[5], $u->toNFKD( $c[1] ), 1, 'NFKD', $verbose );
- $result = verbosify( $c[5], $u->toNFKD( $c[2] ), 2, 'NFKD', $verbose ) && $result;
- $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;
-}
-
-function testInvariant( &$u, $char, $desc, $verbose, $reportFailure = false ) {
- $result = verbosify( $char, $u->toNFC( $char ), 1, 'NFC', $reportFailure );
- $result = verbosify( $char, $u->toNFD( $char ), 1, 'NFD', $reportFailure ) && $result;
- $result = verbosify( $char, $u->toNFKC( $char ), 1, 'NFKC', $reportFailure ) && $result;
- $result = verbosify( $char, $u->toNFKD( $char ), 1, 'NFKD', $reportFailure ) && $result;
- $result = verbosify( $char, $u->cleanUp( $char ), 1, 'cleanUp', $reportFailure ) && $result;
-
- 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
deleted file mode 100644
index 53e68c29..00000000
--- a/includes/normal/UtfNormalTest2.php
+++ /dev/null
@@ -1,275 +0,0 @@
-#!/usr/bin/env php
-<?php
-/**
- * Other tests for the unicode normalization module.
- *
- * This 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" );
-}
-
-// From http://unicode.org/Public/UNIDATA/NormalizationTest.txt
-$file = "NormalizationTest.txt";
-
-// Anything after this character is a comment
-define ( 'COMMENT', '#' );
-
-// Semicolons are used to separate the columns
-define ( 'SEPARATOR', ';' );
-
-$f = fopen( $file, "r" );
-
-/**
- * The following section will be used for testing different normalization methods.
- * - Pure PHP
- * ~ 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 Ubuntu 3.8.1-3ubuntu1.1 php 5.2.6-3ubuntu4.5
- * ~ 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
- * - libicu Debian 4.4.1-5 php 5.3.2-1ubuntu4.2
- * ~ no assertion errors
- * ~ 13 seconds
- * - Tests comparing pure PHP output with libicu output were added
- * 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 );
-}
-
-/**
- * This set of functions is only useful if youve added a param to the
- * following functions to force pure PHP usage. I decided not to
- * commit that code since might produce a slowdown in the UTF
- * 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" );
-}
-
-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 ) {
- $lineNo++;
-
- if ( count( $col ) == 6 ) {
- $count++;
- 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' );
-
- # c2 == NFC(c1) == NFC(c2) == NFC(c3)
- 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' );
-
- # c3 == NFD(c1) == NFD(c2) == NFD(c3)
- 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' );
-
- # 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' );
-
- # 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' );
- }
-}
-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 );
- } else {
- return false;
- }
-}
-
-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 );
- $pos = strpos( $row, COMMENT );
- $pos2 = strpos( $row, ")" );
- if ( $pos === 0 ) return array( $row );
- $c = "";
-
- 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 );
- }
- }
- $ret[] = $c;
-
- return $ret;
-}
diff --git a/includes/objectcache/MemcachedBagOStuff.php b/includes/objectcache/MemcachedBagOStuff.php
index 53edcdde..83bee700 100644
--- a/includes/objectcache/MemcachedBagOStuff.php
+++ b/includes/objectcache/MemcachedBagOStuff.php
@@ -84,18 +84,17 @@ class MemcachedBagOStuff extends BagOStuff {
* @param int $exptime
* @return bool
*/
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ protected function cas( $casToken, $key, $value, $exptime = 0 ) {
return $this->client->cas( $casToken, $this->encodeKey( $key ),
$value, $this->fixExpiry( $exptime ) );
}
/**
* @param string $key
- * @param int $time
* @return bool
*/
- public function delete( $key, $time = 0 ) {
- return $this->client->delete( $this->encodeKey( $key ), $time );
+ public function delete( $key ) {
+ return $this->client->delete( $this->encodeKey( $key ) );
}
/**
@@ -109,6 +108,14 @@ class MemcachedBagOStuff extends BagOStuff {
$this->fixExpiry( $exptime ) );
}
+ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !is_callable( $callback ) ) {
+ throw new Exception( "Got invalid callback." );
+ }
+
+ return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ }
+
/**
* Get the underlying client object. This is provided for debugging
* purposes.
@@ -145,7 +152,7 @@ class MemcachedBagOStuff extends BagOStuff {
* TTLs higher than 30 days will be detected as absolute TTLs
* (UNIX timestamps), and will result in the cache entry being
* discarded immediately because the expiry is in the past.
- * Clamp expiries >30d at 30d, unless they're >=1e9 in which
+ * Clamp expires >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
@@ -174,6 +181,6 @@ class MemcachedBagOStuff extends BagOStuff {
* @param string $text
*/
protected function debugLog( $text ) {
- wfDebugLog( 'memcached', $text );
+ $this->logger->debug( $text );
}
}
diff --git a/includes/objectcache/MemcachedClient.php b/includes/objectcache/MemcachedClient.php
index 41eebfb5..bc4a00b2 100644
--- a/includes/objectcache/MemcachedClient.php
+++ b/includes/objectcache/MemcachedClient.php
@@ -64,6 +64,9 @@
* @version 0.1.2
*/
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
// {{{ requirements
// }}}
@@ -233,6 +236,11 @@ class MWMemcached {
*/
public $_connect_attempts;
+ /**
+ * @var LoggerInterface
+ */
+ private $_logger;
+
// }}}
// }}}
// {{{ methods
@@ -263,6 +271,8 @@ class MWMemcached {
$this->_connect_timeout = isset( $args['connect_timeout'] ) ? $args['connect_timeout'] : 0.1;
$this->_connect_attempts = 2;
+
+ $this->_logger = isset( $args['logger'] ) ? $args['logger'] : new NullLogger();
}
// }}}
@@ -413,7 +423,6 @@ class MWMemcached {
* @return mixed
*/
public function get( $key, &$casToken = null ) {
- wfProfileIn( __METHOD__ );
if ( $this->_debug ) {
$this->_debugprint( "get($key)\n" );
@@ -421,19 +430,16 @@ class MWMemcached {
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;
}
$sock = $this->get_sock( $key );
if ( !is_resource( $sock ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -446,7 +452,6 @@ class MWMemcached {
$cmd = "gets $key\r\n";
if ( !$this->_fwrite( $sock, $cmd ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -463,7 +468,6 @@ class MWMemcached {
if ( isset( $val[$key] ) ) {
$value = $val[$key];
}
- wfProfileOut( __METHOD__ );
return $value;
}
@@ -1110,14 +1114,14 @@ class MWMemcached {
* @param string $text
*/
function _debugprint( $text ) {
- wfDebugLog( 'memcached', $text );
+ $this->_logger->debug( $text );
}
/**
* @param string $text
*/
function _error_log( $text ) {
- wfDebugLog( 'memcached-serious', "Memcached error: $text" );
+ $this->_logger->error( "Memcached error: $text" );
}
/**
diff --git a/includes/objectcache/MemcachedPeclBagOStuff.php b/includes/objectcache/MemcachedPeclBagOStuff.php
index c853bcf4..f2c49281 100644
--- a/includes/objectcache/MemcachedPeclBagOStuff.php
+++ b/includes/objectcache/MemcachedPeclBagOStuff.php
@@ -43,8 +43,10 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* values, but serialization is much slower unless the php.ini option
* igbinary.compact_strings is off.
* @param array $params
+ * @throws MWException
*/
function __construct( $params ) {
+ parent::__construct( $params );
$params = $this->applyDefaultParams( $params );
if ( $params['persistent'] ) {
@@ -53,7 +55,7 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
// We can only reuse a pool ID if we keep the config consistent.
$this->client = new Memcached( md5( serialize( $params ) ) );
if ( count( $this->client->getServerList() ) ) {
- wfDebug( __METHOD__ . ": persistent Memcached object already loaded.\n" );
+ $this->logger->debug( __METHOD__ . ": persistent Memcached object already loaded." );
return; // already initialized; don't add duplicate servers
}
} else {
@@ -119,11 +121,9 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* @return mixed
*/
public function get( $key, &$casToken = null ) {
- wfProfileIn( __METHOD__ );
$this->debugLog( "get($key)" );
$result = $this->client->get( $this->encodeKey( $key ), null, $casToken );
$result = $this->checkResult( $key, $result );
- wfProfileOut( __METHOD__ );
return $result;
}
@@ -145,19 +145,18 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* @param int $exptime
* @return bool
*/
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ protected function cas( $casToken, $key, $value, $exptime = 0 ) {
$this->debugLog( "cas($key)" );
return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
}
/**
* @param string $key
- * @param int $time
* @return bool
*/
- public function delete( $key, $time = 0 ) {
+ public function delete( $key ) {
$this->debugLog( "delete($key)" );
- $result = parent::delete( $key, $time );
+ $result = parent::delete( $key );
if ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) {
// "Not found" is counted as success in our interface
return true;
@@ -224,14 +223,16 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
break;
default:
$msg = $this->client->getResultMessage();
+ $logCtx = array();
if ( $key !== false ) {
$server = $this->client->getServerByKey( $key );
- $serverName = "{$server['host']}:{$server['port']}";
- $msg = "Memcached error for key \"$key\" on server \"$serverName\": $msg";
+ $logCtx['memcached-server'] = "{$server['host']}:{$server['port']}";
+ $logCtx['memcached-key'] = $key;
+ $msg = "Memcached error for key \"{memcached-key}\" on server \"{memcached-server}\": $msg";
} else {
$msg = "Memcached error: $msg";
}
- wfDebugLog( 'memcached-serious', $msg );
+ $this->logger->error( $msg, $logCtx );
$this->setLastError( BagOStuff::ERR_UNEXPECTED );
}
return $result;
@@ -242,11 +243,9 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* @return array
*/
public function getMulti( array $keys ) {
- wfProfileIn( __METHOD__ );
$this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
$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 );
}
@@ -257,7 +256,6 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
* @return bool
*/
public function setMulti( array $data, $exptime = 0 ) {
- wfProfileIn( __METHOD__ );
foreach ( $data as $key => $value ) {
$encKey = $this->encodeKey( $key );
if ( $encKey !== $key ) {
@@ -267,7 +265,6 @@ class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
}
$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 330d2b52..6fba61ba 100644
--- a/includes/objectcache/MemcachedPhpBagOStuff.php
+++ b/includes/objectcache/MemcachedPhpBagOStuff.php
@@ -42,6 +42,7 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
* @param array $params
*/
function __construct( $params ) {
+ parent::__construct( $params );
$params = $this->applyDefaultParams( $params );
$this->client = new MemCachedClientforWiki( $params );
@@ -67,23 +68,6 @@ class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
/**
* @param string $key
- * @param int $timeout
- * @return bool
- */
- public function lock( $key, $timeout = 0 ) {
- return $this->client->lock( $this->encodeKey( $key ), $timeout );
- }
-
- /**
- * @param string $key
- * @return mixed
- */
- public function unlock( $key ) {
- return $this->client->unlock( $this->encodeKey( $key ) );
- }
-
- /**
- * @param string $key
* @param int $value
* @return mixed
*/
diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php
index 6a691379..be54e4d3 100644
--- a/includes/objectcache/MultiWriteBagOStuff.php
+++ b/includes/objectcache/MultiWriteBagOStuff.php
@@ -40,11 +40,12 @@ class MultiWriteBagOStuff extends BagOStuff {
* the documentation of $wgObjectCaches for more detail.
*
* @param array $params
- * @throws MWException
+ * @throws InvalidArgumentException
*/
public function __construct( $params ) {
+ parent::__construct( $params );
if ( !isset( $params['caches'] ) ) {
- throw new MWException( __METHOD__ . ': the caches parameter is required' );
+ throw new InvalidArgumentException( __METHOD__ . ': the caches parameter is required' );
}
$this->caches = array();
@@ -76,17 +77,6 @@ class MultiWriteBagOStuff extends BagOStuff {
}
/**
- * @param mixed $casToken
- * @param string $key
- * @param mixed $value
- * @param mixed $exptime
- * @return bool
- */
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
- throw new MWException( "CAS is not implemented in " . __CLASS__ );
- }
-
- /**
* @param string $key
* @param mixed $value
* @param int $exptime
@@ -98,11 +88,10 @@ class MultiWriteBagOStuff extends BagOStuff {
/**
* @param string $key
- * @param int $time
* @return bool
*/
- public function delete( $key, $time = 0 ) {
- return $this->doWrite( 'delete', $key, $time );
+ public function delete( $key ) {
+ return $this->doWrite( 'delete', $key );
}
/**
@@ -136,12 +125,13 @@ class MultiWriteBagOStuff extends BagOStuff {
/**
* @param string $key
* @param int $timeout
+ * @param int $expiry
* @return bool
*/
- public function lock( $key, $timeout = 0 ) {
+ public function lock( $key, $timeout = 6, $expiry = 6 ) {
// Lock only the first cache, to avoid deadlocks
if ( isset( $this->caches[0] ) ) {
- return $this->caches[0]->lock( $key, $timeout );
+ return $this->caches[0]->lock( $key, $timeout, $expiry );
} else {
return true;
}
@@ -161,12 +151,12 @@ class MultiWriteBagOStuff extends BagOStuff {
/**
* @param string $key
- * @param Closure $callback Callback method to be executed
+ * @param callable $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
*/
- public function merge( $key, Closure $callback, $exptime = 0, $attempts = 10 ) {
+ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
return $this->doWrite( 'merge', $key, $callback, $exptime );
}
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
index 633b34a2..2e47e24a 100644
--- a/includes/objectcache/ObjectCache.php
+++ b/includes/objectcache/ObjectCache.php
@@ -21,6 +21,8 @@
* @ingroup Cache
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* Functions to get cache objects
*
@@ -81,6 +83,13 @@ class ObjectCache {
* @return BagOStuff
*/
static function newFromParams( $params ) {
+ if ( isset( $params['loggroup'] ) ) {
+ $params['logger'] = LoggerFactory::getInstance( $params['loggroup'] );
+ } else {
+ // For backwards-compatability with custom parameters, lets not
+ // have all logging suddenly disappear
+ $params['logger'] = LoggerFactory::getInstance( 'objectcache' );
+ }
if ( isset( $params['factory'] ) ) {
return call_user_func( $params['factory'], $params );
} elseif ( isset( $params['class'] ) ) {
@@ -135,7 +144,7 @@ class ObjectCache {
} elseif ( function_exists( 'wincache_ucache_get' ) ) {
$id = 'wincache';
} else {
- if ( $fallback ) {
+ if ( $fallback !== null ) {
return self::newFromId( $fallback );
}
throw new MWException( "CACHE_ACCEL requested but no suitable object " .
diff --git a/includes/objectcache/ObjectCacheSessionHandler.php b/includes/objectcache/ObjectCacheSessionHandler.php
index cdf8da1e..789f1e3b 100644
--- a/includes/objectcache/ObjectCacheSessionHandler.php
+++ b/includes/objectcache/ObjectCacheSessionHandler.php
@@ -28,6 +28,9 @@
* @ingroup Cache
*/
class ObjectCacheSessionHandler {
+ /** @var array Map of (session ID => SHA-1 of the data) */
+ protected static $hashCache = array();
+
/**
* Install a session handler for the current web request
*/
@@ -49,10 +52,11 @@ class ObjectCacheSessionHandler {
/**
* Get the cache storage object to use for session storage
- * @return ObjectCache
+ * @return BagOStuff
*/
- static function getCache() {
+ protected static function getCache() {
global $wgSessionCacheType;
+
return ObjectCache::getInstance( $wgSessionCacheType );
}
@@ -62,11 +66,19 @@ class ObjectCacheSessionHandler {
* @param string $id Session id
* @return string Cache key
*/
- static function getKey( $id ) {
+ protected static function getKey( $id ) {
return wfMemcKey( 'session', $id );
}
/**
+ * @param mixed $data
+ * @return string
+ */
+ protected static function getHash( $data ) {
+ return sha1( serialize( $data ) );
+ }
+
+ /**
* Callback when opening a session.
*
* @param string $save_path Path used to store session files, unused
@@ -95,22 +107,29 @@ class ObjectCacheSessionHandler {
*/
static function read( $id ) {
$data = self::getCache()->get( self::getKey( $id ) );
- if ( $data === false ) {
- return '';
- }
- return $data;
+
+ self::$hashCache = array( $id => self::getHash( $data ) );
+
+ return ( $data === false ) ? '' : $data;
}
/**
* Callback when writing session data.
*
* @param string $id Session id
- * @param mixed $data Session data
+ * @param string $data Session data
* @return bool Success
*/
static function write( $id, $data ) {
global $wgObjectCacheSessionExpiry;
- self::getCache()->set( self::getKey( $id ), $data, $wgObjectCacheSessionExpiry );
+
+ // Only issue a write if anything changed (PHP 5.6 already does this)
+ if ( !isset( self::$hashCache[$id] )
+ || self::getHash( $data ) !== self::$hashCache[$id]
+ ) {
+ self::getCache()->set( self::getKey( $id ), $data, $wgObjectCacheSessionExpiry );
+ }
+
return true;
}
@@ -122,6 +141,7 @@ class ObjectCacheSessionHandler {
*/
static function destroy( $id ) {
self::getCache()->delete( self::getKey( $id ) );
+
return true;
}
diff --git a/includes/objectcache/RedisBagOStuff.php b/includes/objectcache/RedisBagOStuff.php
index ae8cc5b7..de3511df 100644
--- a/includes/objectcache/RedisBagOStuff.php
+++ b/includes/objectcache/RedisBagOStuff.php
@@ -56,6 +56,7 @@ class RedisBagOStuff extends BagOStuff {
* @param array $params
*/
function __construct( $params ) {
+ parent::__construct( $params );
$redisConf = array( 'serializer' => 'none' ); // manage that in this class
foreach ( array( 'connectTimeout', 'persistent', 'password' ) as $opt ) {
if ( isset( $params[$opt] ) ) {
@@ -73,7 +74,6 @@ class RedisBagOStuff extends BagOStuff {
}
public function get( $key, &$casToken = null ) {
- $section = new ProfileSection( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
@@ -93,7 +93,6 @@ class RedisBagOStuff extends BagOStuff {
}
public function set( $key, $value, $expiry = 0 ) {
- $section = new ProfileSection( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
@@ -116,8 +115,7 @@ class RedisBagOStuff extends BagOStuff {
return $result;
}
- public function cas( $casToken, $key, $value, $expiry = 0 ) {
- $section = new ProfileSection( __METHOD__ );
+ protected function cas( $casToken, $key, $value, $expiry = 0 ) {
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
@@ -150,8 +148,7 @@ class RedisBagOStuff extends BagOStuff {
return $result;
}
- public function delete( $key, $time = 0 ) {
- $section = new ProfileSection( __METHOD__ );
+ public function delete( $key ) {
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
@@ -171,7 +168,6 @@ class RedisBagOStuff extends BagOStuff {
}
public function getMulti( array $keys ) {
- $section = new ProfileSection( __METHOD__ );
$batches = array();
$conns = array();
@@ -217,7 +213,6 @@ class RedisBagOStuff extends BagOStuff {
* @return bool
*/
public function setMulti( array $data, $expiry = 0 ) {
- $section = new ProfileSection( __METHOD__ );
$batches = array();
$conns = array();
@@ -265,7 +260,6 @@ class RedisBagOStuff extends BagOStuff {
public function add( $key, $value, $expiry = 0 ) {
- $section = new ProfileSection( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
@@ -303,7 +297,6 @@ class RedisBagOStuff extends BagOStuff {
* @return int|bool New value or false on failure
*/
public function incr( $key, $value = 1 ) {
- $section = new ProfileSection( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
@@ -322,6 +315,15 @@ class RedisBagOStuff extends BagOStuff {
$this->logRequest( 'incr', $key, $server, $result );
return $result;
}
+
+ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !is_callable( $callback ) ) {
+ throw new Exception( "Got invalid callback." );
+ }
+
+ return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ }
+
/**
* @param mixed $data
* @return string
@@ -371,7 +373,7 @@ class RedisBagOStuff extends BagOStuff {
* @param string $msg
*/
protected function logError( $msg ) {
- wfDebugLog( 'redis', "Redis error: $msg" );
+ $this->logger->error( "Redis error: $msg" );
}
/**
diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php
index 58720790..82eeb842 100644
--- a/includes/objectcache/SqlBagOStuff.php
+++ b/includes/objectcache/SqlBagOStuff.php
@@ -88,6 +88,7 @@ class SqlBagOStuff extends BagOStuff {
* @param array $params
*/
public function __construct( $params ) {
+ parent::__construct( $params );
if ( isset( $params['servers'] ) ) {
$this->serverInfos = $params['servers'];
$this->numServers = count( $this->serverInfos );
@@ -118,6 +119,7 @@ class SqlBagOStuff extends BagOStuff {
*
* @param int $serverIndex
* @return DatabaseBase
+ * @throws MWException
*/
protected function getDB( $serverIndex ) {
global $wgDebugDBTransactions;
@@ -137,12 +139,14 @@ class SqlBagOStuff extends BagOStuff {
# If server connection info was given, use that
if ( $this->serverInfos ) {
if ( $wgDebugDBTransactions ) {
- wfDebug( "Using provided serverInfo for SqlBagOStuff\n" );
+ $this->logger->debug( "Using provided serverInfo for SqlBagOStuff" );
}
$info = $this->serverInfos[$serverIndex];
$type = isset( $info['type'] ) ? $info['type'] : 'mysql';
$host = isset( $info['host'] ) ? $info['host'] : '[unknown]';
- wfDebug( __CLASS__ . ": connecting to $host\n" );
+ $this->logger->debug( __CLASS__ . ": connecting to $host" );
+ // Use a blank trx profiler to ignore expections as this is a cache
+ $info['trxProfiler'] = new TransactionProfiler();
$db = DatabaseBase::factory( $type, $info );
$db->clearFlag( DBO_TRX );
} else {
@@ -160,7 +164,7 @@ class SqlBagOStuff extends BagOStuff {
}
}
if ( $wgDebugDBTransactions ) {
- wfDebug( sprintf( "Connection %s will be used for SqlBagOStuff\n", $db ) );
+ $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
}
$this->conns[$serverIndex] = $db;
}
@@ -322,9 +326,7 @@ class SqlBagOStuff extends BagOStuff {
if ( $exptime == 0 ) {
$encExpiry = $this->getMaxDateTime( $db );
} else {
- if ( $exptime < 3.16e8 ) { # ~10 years
- $exptime += time();
- }
+ $exptime = $this->convertExpiry( $exptime );
$encExpiry = $db->timestamp( $exptime );
}
foreach ( $serverKeys as $tableName => $tableKeys ) {
@@ -377,10 +379,7 @@ class SqlBagOStuff extends BagOStuff {
if ( $exptime == 0 ) {
$encExpiry = $this->getMaxDateTime( $db );
} else {
- if ( $exptime < 3.16e8 ) { # ~10 years
- $exptime += time();
- }
-
+ $exptime = $this->convertExpiry( $exptime );
$encExpiry = $db->timestamp( $exptime );
}
// (bug 24425) use a replace if the db supports it instead of
@@ -408,7 +407,7 @@ class SqlBagOStuff extends BagOStuff {
* @param int $exptime
* @return bool
*/
- public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ protected function cas( $casToken, $key, $value, $exptime = 0 ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
try {
$db = $this->getDB( $serverIndex );
@@ -421,9 +420,7 @@ class SqlBagOStuff extends BagOStuff {
if ( $exptime == 0 ) {
$encExpiry = $this->getMaxDateTime( $db );
} else {
- if ( $exptime < 3.16e8 ) { # ~10 years
- $exptime += time();
- }
+ $exptime = $this->convertExpiry( $exptime );
$encExpiry = $db->timestamp( $exptime );
}
// (bug 24425) use a replace if the db supports it instead of
@@ -452,10 +449,9 @@ class SqlBagOStuff extends BagOStuff {
/**
* @param string $key
- * @param int $time
* @return bool
*/
- public function delete( $key, $time = 0 ) {
+ public function delete( $key ) {
list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
try {
$db = $this->getDB( $serverIndex );
@@ -520,6 +516,14 @@ class SqlBagOStuff extends BagOStuff {
return $newValue;
}
+ public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !is_callable( $callback ) ) {
+ throw new Exception( "Got invalid callback." );
+ }
+
+ return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ }
+
/**
* @param DatabaseBase $db
* @param string $exptime
@@ -621,7 +625,8 @@ class SqlBagOStuff extends BagOStuff {
if ( $remainingSeconds > $totalSeconds ) {
$totalSeconds = $remainingSeconds;
}
- $percent = ( $i + $remainingSeconds / $totalSeconds )
+ $processedSeconds = $totalSeconds - $remainingSeconds;
+ $percent = ( $i + $processedSeconds / $totalSeconds )
/ $this->shards * 100;
}
$percent = ( $percent / $this->numServers )
@@ -638,6 +643,11 @@ class SqlBagOStuff extends BagOStuff {
return true;
}
+ /**
+ * Delete content of shard tables in every server.
+ * Return true if the operation is successful, false otherwise.
+ * @return bool
+ */
public function deleteAll() {
for ( $serverIndex = 0; $serverIndex < $this->numServers; $serverIndex++ ) {
try {
@@ -702,13 +712,13 @@ class SqlBagOStuff extends BagOStuff {
if ( $exception instanceof DBConnectionError ) {
$this->markServerDown( $exception, $serverIndex );
}
- wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
+ $this->logger->error( "DBError: {$exception->getMessage()}" );
if ( $exception instanceof DBConnectionError ) {
$this->setLastError( BagOStuff::ERR_UNREACHABLE );
- wfDebug( __METHOD__ . ": ignoring connection error\n" );
+ $this->logger->debug( __METHOD__ . ": ignoring connection error" );
} else {
$this->setLastError( BagOStuff::ERR_UNEXPECTED );
- wfDebug( __METHOD__ . ": ignoring query error\n" );
+ $this->logger->debug( __METHOD__ . ": ignoring query error" );
}
}
@@ -723,18 +733,21 @@ class SqlBagOStuff extends BagOStuff {
$this->markServerDown( $exception, $serverIndex );
}
if ( $exception->db && $exception->db->wasReadOnlyError() ) {
- try {
- $exception->db->rollback( __METHOD__ );
- } catch ( DBError $e ) {
+ if ( $exception->db->trxLevel() ) {
+ try {
+ $exception->db->rollback( __METHOD__ );
+ } catch ( DBError $e ) {
+ }
}
}
- wfDebugLog( 'SQLBagOStuff', "DBError: {$exception->getMessage()}" );
+
+ $this->logger->error( "DBError: {$exception->getMessage()}" );
if ( $exception instanceof DBConnectionError ) {
$this->setLastError( BagOStuff::ERR_UNREACHABLE );
- wfDebug( __METHOD__ . ": ignoring connection error\n" );
+ $this->logger->debug( __METHOD__ . ": ignoring connection error" );
} else {
$this->setLastError( BagOStuff::ERR_UNEXPECTED );
- wfDebug( __METHOD__ . ": ignoring query error\n" );
+ $this->logger->debug( __METHOD__ . ": ignoring query error" );
}
}
@@ -750,12 +763,12 @@ class SqlBagOStuff extends BagOStuff {
unset( $this->connFailureTimes[$serverIndex] );
unset( $this->connFailureErrors[$serverIndex] );
} else {
- wfDebug( __METHOD__ . ": Server #$serverIndex already down\n" );
+ $this->logger->debug( __METHOD__ . ": Server #$serverIndex already down" );
return;
}
}
$now = time();
- wfDebug( __METHOD__ . ": Server #$serverIndex down until " . ( $now + 60 ) . "\n" );
+ $this->logger->info( __METHOD__ . ": Server #$serverIndex down until " . ( $now + 60 ) );
$this->connFailureTimes[$serverIndex] = $now;
$this->connFailureErrors[$serverIndex] = $exception;
}
@@ -779,9 +792,3 @@ class SqlBagOStuff extends BagOStuff {
}
}
}
-
-/**
- * Backwards compatibility alias
- */
-class MediaWikiBagOStuff extends SqlBagOStuff {
-}
diff --git a/includes/page/Article.php b/includes/page/Article.php
index 4e753817..4cde5ad8 100644
--- a/includes/page/Article.php
+++ b/includes/page/Article.php
@@ -30,8 +30,6 @@
* See design.txt for an overview.
* Note: edit user interface and cache support functions have been
* moved to separate EditPage and HTMLFileCache classes.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class Article implements Page {
/** @var IContextSource The context this Article is executed in */
@@ -120,7 +118,7 @@ class Article implements Page {
}
$page = null;
- wfRunHooks( 'ArticleFromTitle', array( &$title, &$page, $context ) );
+ Hooks::run( 'ArticleFromTitle', array( &$title, &$page, $context ) );
if ( !$page ) {
switch ( $title->getNamespace() ) {
case NS_FILE:
@@ -226,7 +224,6 @@ class Article implements Page {
* @since 1.21
*/
protected function getContentObject() {
- wfProfileIn( __METHOD__ );
if ( $this->mPage->getID() === 0 ) {
# If this is a MediaWiki:x message, then load the messages
@@ -247,7 +244,6 @@ class Article implements Page {
$content = $this->mContentObject;
}
- wfProfileOut( __METHOD__ );
return $content;
}
@@ -344,12 +340,9 @@ class Article implements Page {
return $this->mContent;
}
- wfProfileIn( __METHOD__ );
-
$content = $this->fetchContentObject();
if ( !$content ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -357,8 +350,6 @@ class Article implements Page {
$this->mContent = ContentHandler::getContentText( $content );
ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
- wfProfileOut( __METHOD__ );
-
return $this->mContent;
}
@@ -379,8 +370,6 @@ class Article implements Page {
return $this->mContentObject;
}
- wfProfileIn( __METHOD__ );
-
$this->mContentLoaded = true;
$this->mContent = null;
@@ -389,7 +378,7 @@ class Article implements Page {
# Pre-fill content with error message so that if something
# fails we'll have something telling us what we intended.
//XXX: this isn't page content but a UI message. horrible.
- $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), array() );
+ $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ) );
if ( $oldid ) {
# $this->mRevision might already be fetched by getOldIDFromRequest()
@@ -397,24 +386,24 @@ class Article implements Page {
$this->mRevision = Revision::newFromId( $oldid );
if ( !$this->mRevision ) {
wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
- wfProfileOut( __METHOD__ );
return false;
}
}
} else {
- if ( !$this->mPage->getLatest() ) {
+ $oldid = $this->mPage->getLatest();
+ if ( !$oldid ) {
wfDebug( __METHOD__ . " failed to find page data for title " .
$this->getTitle()->getPrefixedText() . "\n" );
- wfProfileOut( __METHOD__ );
return false;
}
+ # Update error message with correct oldid
+ $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ) );
+
$this->mRevision = $this->mPage->getRevision();
if ( !$this->mRevision ) {
- wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " .
- $this->mPage->getLatest() . "\n" );
- wfProfileOut( __METHOD__ );
+ wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" );
return false;
}
}
@@ -422,15 +411,21 @@ 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...
// Loads if user is allowed
- $this->mContentObject = $this->mRevision->getContent(
+ $content = $this->mRevision->getContent(
Revision::FOR_THIS_USER,
$this->getContext()->getUser()
);
- $this->mRevIdFetched = $this->mRevision->getId();
- wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
+ if ( !$content ) {
+ wfDebug( __METHOD__ . " failed to retrieve content of revision " .
+ $this->mRevision->getId() . "\n" );
+ return false;
+ }
+
+ $this->mContentObject = $content;
+ $this->mRevIdFetched = $this->mRevision->getId();
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
return $this->mContentObject;
}
@@ -482,8 +477,6 @@ class Article implements Page {
public function view() {
global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects;
- wfProfileIn( __METHOD__ );
-
# Get variables from query string
# As side effect this will load the revision and update the title
# in a revision ID is passed in the request, so this should remain
@@ -495,7 +488,6 @@ class Article implements Page {
$permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
if ( count( $permErrors ) ) {
wfDebug( __METHOD__ . ": denied on secondary read check\n" );
- wfProfileOut( __METHOD__ );
throw new PermissionsError( 'read', $permErrors );
}
@@ -504,7 +496,6 @@ class Article implements Page {
if ( $this->mRedirectUrl ) {
$outputPage->redirect( $this->mRedirectUrl );
wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
- wfProfileOut( __METHOD__ );
return;
}
@@ -513,7 +504,6 @@ class Article implements Page {
if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
wfDebug( __METHOD__ . ": showing diff page\n" );
$this->showDiffPage();
- wfProfileOut( __METHOD__ );
return;
}
@@ -568,7 +558,6 @@ class Article implements Page {
# Is it client cached?
if ( $outputPage->checkLastModified( $timestamp ) ) {
wfDebug( __METHOD__ . ": done 304\n" );
- wfProfileOut( __METHOD__ );
return;
# Try file cache
@@ -577,7 +566,6 @@ class Article implements Page {
# tell wgOut that output is taken care of
$outputPage->disable();
$this->mPage->doViewUpdates( $user, $oldid );
- wfProfileOut( __METHOD__ );
return;
}
@@ -587,7 +575,7 @@ class Article implements Page {
$useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $user->getStubThreshold() ) {
- wfIncrStats( 'pcache_miss_stub' );
+ $this->getContext()->getStats()->increment( 'pcache_miss_stub' );
}
$this->showRedirectedFromHeader();
@@ -602,7 +590,7 @@ class Article implements Page {
while ( !$outputDone && ++$pass ) {
switch ( $pass ) {
case 1:
- wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
+ Hooks::run( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
break;
case 2:
# Early abort if the page doesn't exist
@@ -610,7 +598,6 @@ class Article implements Page {
wfDebug( __METHOD__ . ": showing missing article\n" );
$this->showMissingArticle();
$this->mPage->doViewUpdates( $user );
- wfProfileOut( __METHOD__ );
return;
}
@@ -649,7 +636,6 @@ class Article implements Page {
if ( !$this->showDeletedRevisionHeader() ) {
wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
- wfProfileOut( __METHOD__ );
return;
}
}
@@ -665,7 +651,7 @@ class Article implements Page {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
- } elseif ( !wfRunHooks( 'ArticleContentViewCustom',
+ } elseif ( !Hooks::run( 'ArticleContentViewCustom',
array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
# Allow extensions do their own custom view for certain pages
@@ -696,16 +682,14 @@ class Article implements Page {
$outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
}
# Connection or timeout error
- wfProfileOut( __METHOD__ );
return;
}
$this->mParserOutput = $poolArticleView->getParserOutput();
$outputPage->addParserOutput( $this->mParserOutput );
if ( $content->getRedirectTarget() ) {
- $outputPage->addSubtitle(
- "<span id=\"redirectsub\">" . wfMessage( 'redirectpagesub' )->parse() . "</span>"
- );
+ $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
+ $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
}
# Don't cache a dirty ParserOutput object
@@ -724,7 +708,7 @@ class Article implements Page {
}
# Get the ParserOutput actually *displayed* here.
- # Note that $this->mParserOutput is the *current* version output.
+ # Note that $this->mParserOutput is the *current*/oldid version output.
$pOutput = ( $outputDone instanceof ParserOutput )
? $outputDone // object fetched by hook
: $this->mParserOutput;
@@ -755,7 +739,6 @@ class Article implements Page {
$outputPage->addModules( 'mediawiki.action.view.postEdit' );
- wfProfileOut( __METHOD__ );
}
/**
@@ -774,9 +757,8 @@ class Article implements Page {
* Show a diff page according to current request variables. For use within
* Article::view() only, other callers should use the DifferenceEngine class.
*
- * @todo Make protected
*/
- public function showDiffPage() {
+ protected function showDiffPage() {
$request = $this->getContext()->getRequest();
$user = $this->getContext()->getUser();
$diff = $request->getVal( 'diff' );
@@ -790,7 +772,11 @@ class Article implements Page {
if ( !$rev ) {
$this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
- $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
+ $msg = $this->getContext()->msg( 'difference-missing-revision' )
+ ->params( $oldid )
+ ->numParams( 1 )
+ ->parseAsBlock();
+ $this->getContext()->getOutput()->addHtml( $msg );
return;
}
@@ -831,7 +817,7 @@ class Article implements Page {
if ( $showCacheHint ) {
$dir = $this->getContext()->getLanguage()->getDir();
- $lang = $this->getContext()->getLanguage()->getCode();
+ $lang = $this->getContext()->getLanguage()->getHtmlCode();
$outputPage->wrapWikiMsg(
"<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
@@ -973,9 +959,10 @@ class Article implements Page {
*/
public function showRedirectedFromHeader() {
global $wgRedirectSources;
- $outputPage = $this->getContext()->getOutput();
- $request = $this->getContext()->getRequest();
+ $context = $this->getContext();
+ $outputPage = $context->getOutput();
+ $request = $context->getRequest();
$rdfrom = $request->getVal( 'rdfrom' );
// Construct a URL for the current page view, but with the target title
@@ -991,7 +978,7 @@ class Article implements Page {
if ( isset( $this->mRedirectedFrom ) ) {
// This is an internally redirected page view.
// We'll need a backlink to the source page for navigation.
- if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
+ if ( Hooks::run( 'ArticleViewRedirect', array( &$this ) ) ) {
$redir = Linker::linkKnown(
$this->mRedirectedFrom,
null,
@@ -999,7 +986,9 @@ class Article implements Page {
array( 'redirect' => 'no' )
);
- $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
+ $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
+ $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
+ . "</span>" );
// Add the script to update the displayed URL and
// set the fragment if one was specified in the redirect
@@ -1021,7 +1010,9 @@ class Article implements Page {
// If it was reported from a trusted site, supply a backlink.
if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
$redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
- $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
+ $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
+ $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
+ . "</span>" );
// Add the script to update the displayed URL
$outputPage->addJsConfigVars( array(
@@ -1065,7 +1056,7 @@ class Article implements Page {
// Show a footer allowing the user to patrol the shown revision or page if possible
$patrolFooterShown = $this->showPatrolFooter();
- wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
+ Hooks::run( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
}
/**
@@ -1092,8 +1083,6 @@ class Article implements Page {
return false;
}
- wfProfileIn( __METHOD__ );
-
// New page patrol: Get the timestamp of the oldest revison which
// the revision table holds for the given page. Then we look
// whether it's within the RC lifespan and if it is, we try
@@ -1102,7 +1091,6 @@ class Article implements Page {
// Check for cached results
if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -1111,7 +1099,6 @@ class Article implements Page {
) {
// 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__ );
return false;
}
@@ -1147,14 +1134,12 @@ class Article implements Page {
// Don't cache in case we can patrol as this could change
$cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
- wfProfileOut( __METHOD__ );
return false;
}
- if ( $rc->getPerformer()->getName() == $user->getName() ) {
+ if ( $rc->getPerformer()->equals( $user ) ) {
// Don't show a patrol link for own creations. If the user could
// patrol them, they already would be patrolled
- wfProfileOut( __METHOD__ );
return false;
}
@@ -1184,7 +1169,6 @@ class Article implements Page {
'</div>'
);
- wfProfileOut( __METHOD__ );
return true;
}
@@ -1214,7 +1198,8 @@ class Article implements Page {
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 ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # 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',
@@ -1235,24 +1220,20 @@ class Article implements Page {
}
}
- wfRunHooks( 'ShowMissingArticle', array( $this ) );
+ Hooks::run( '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 ) );
+ Hooks::run( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) );
# Show delete and move logs
- $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' ) )
- );
- }
+ 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
@@ -1265,7 +1246,7 @@ class Article implements Page {
$outputPage->setIndexPolicy( $policy['index'] );
$outputPage->setFollowPolicy( $policy['follow'] );
- $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
+ $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', array( $this ) );
if ( !$hookResult ) {
return;
@@ -1341,11 +1322,12 @@ class Article implements Page {
* @param int $oldid Revision ID of this article revision
*/
public function setOldSubtitle( $oldid = 0 ) {
- if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
+ if ( !Hooks::run( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
return;
}
- $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1;
+ $context = $this->getContext();
+ $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
# Cascade unhide param in links for easy deletion browsing
$extraParams = array();
@@ -1362,8 +1344,8 @@ class Article implements Page {
$timestamp = $revision->getTimestamp();
$current = ( $oldid == $this->mPage->getLatest() );
- $language = $this->getContext()->getLanguage();
- $user = $this->getContext()->getUser();
+ $language = $context->getLanguage();
+ $user = $context->getUser();
$td = $language->userTimeAndDate( $timestamp, $user );
$tddate = $language->userDate( $timestamp, $user );
@@ -1372,28 +1354,33 @@ class Article implements Page {
# Show user links if allowed to see them. If hidden, then show them only if requested...
$userlinks = Linker::revUserTools( $revision, !$unhide );
- $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
+ $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
? 'revision-info-current'
: 'revision-info';
- $outputPage = $this->getContext()->getOutput();
- $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
- $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
- $tdtime, $revision->getUserText() )->rawParams( Linker::revComment( $revision, true, true ) )->parse() . "</div>" );
+ $outputPage = $context->getOutput();
+ $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" .
+ $context->msg( $infomsg, $td )
+ ->rawParams( $userlinks )
+ ->params( $revision->getID(), $tddate, $tdtime, $revision->getUserText() )
+ ->rawParams( Linker::revComment( $revision, true, true ) )
+ ->parse() .
+ "</div>"
+ );
$lnk = $current
- ? wfMessage( 'currentrevisionlink' )->escaped()
+ ? $context->msg( 'currentrevisionlink' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'currentrevisionlink' )->escaped(),
+ $context->msg( 'currentrevisionlink' )->escaped(),
array(),
$extraParams
);
$curdiff = $current
- ? wfMessage( 'diff' )->escaped()
+ ? $context->msg( 'diff' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'diff' )->escaped(),
+ $context->msg( 'diff' )->escaped(),
array(),
array(
'diff' => 'cur',
@@ -1404,30 +1391,30 @@ class Article implements Page {
$prevlink = $prev
? Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'previousrevision' )->escaped(),
+ $context->msg( 'previousrevision' )->escaped(),
array(),
array(
'direction' => 'prev',
'oldid' => $oldid
) + $extraParams
)
- : wfMessage( 'previousrevision' )->escaped();
+ : $context->msg( 'previousrevision' )->escaped();
$prevdiff = $prev
? Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'diff' )->escaped(),
+ $context->msg( 'diff' )->escaped(),
array(),
array(
'diff' => 'prev',
'oldid' => $oldid
) + $extraParams
)
- : wfMessage( 'diff' )->escaped();
+ : $context->msg( 'diff' )->escaped();
$nextlink = $current
- ? wfMessage( 'nextrevision' )->escaped()
+ ? $context->msg( 'nextrevision' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'nextrevision' )->escaped(),
+ $context->msg( 'nextrevision' )->escaped(),
array(),
array(
'direction' => 'next',
@@ -1435,10 +1422,10 @@ class Article implements Page {
) + $extraParams
);
$nextdiff = $current
- ? wfMessage( 'diff' )->escaped()
+ ? $context->msg( 'diff' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'diff' )->escaped(),
+ $context->msg( 'diff' )->escaped(),
array(),
array(
'diff' => 'next',
@@ -1452,7 +1439,7 @@ class Article implements Page {
}
$outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
- wfMessage( 'revision-nav' )->rawParams(
+ $context->msg( 'revision-nav' )->rawParams(
$prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
)->escaped() . "</div>" );
}
@@ -1472,7 +1459,7 @@ class Article implements Page {
$lang = $this->getTitle()->getPageLanguage();
$out = $this->getContext()->getOutput();
if ( $appendSubtitle ) {
- $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
+ $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
}
$out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
@@ -1508,8 +1495,9 @@ class Article implements Page {
( $forceKnown ? array( 'known', 'noclasses' ) : array() )
) . '</li>';
}
+ $html .= '</ul>';
- $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->text();
+ $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
return '<div class="redirectMsg">' .
'<p>' . $redirectToText . '</p>' .
@@ -1518,6 +1506,28 @@ class Article implements Page {
}
/**
+ * Adds help link with an icon via page indicators.
+ * Link target can be overridden by a local message containing a wikilink:
+ * the message key is: 'namespace-' + namespace number + '-helppage'.
+ * @param string $to Target MediaWiki.org page title or encoded URL.
+ * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o.
+ * @since 1.25
+ */
+ public function addHelpLink( $to, $overrideBaseUrl = false ) {
+ $msg = wfMessage(
+ 'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
+ );
+
+ $out = $this->getContext()->getOutput();
+ if ( !$msg->isDisabled() ) {
+ $helpUrl = Skin::makeUrl( $msg->plain() );
+ $out->addHelpLink( $helpUrl, true );
+ } else {
+ $out->addHelpLink( $to, $overrideBaseUrl );
+ }
+ }
+
+ /**
* Handle action=render
*/
public function render() {
@@ -1549,7 +1559,8 @@ class Article implements Page {
# This code desperately needs to be totally rewritten
$title = $this->getTitle();
- $user = $this->getContext()->getUser();
+ $context = $this->getContext();
+ $user = $context->getUser();
# Check permissions
$permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
@@ -1566,8 +1577,8 @@ class Article implements Page {
$this->mPage->loadPageData( 'fromdbmaster' );
if ( !$this->mPage->exists() ) {
$deleteLogPage = new LogPage( 'delete' );
- $outputPage = $this->getContext()->getOutput();
- $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
+ $outputPage = $context->getOutput();
+ $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
$outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
);
@@ -1583,7 +1594,7 @@ class Article implements Page {
return;
}
- $request = $this->getContext()->getRequest();
+ $request = $context->getRequest();
$deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
$deleteReason = $request->getText( 'wpReason' );
@@ -1615,7 +1626,7 @@ class Article implements Page {
if ( !$reason ) {
try {
$reason = $this->generateReason( $hasHistory );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
# 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" );
@@ -1627,10 +1638,11 @@ class Article implements Page {
if ( $hasHistory ) {
$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).
+ // 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',
@@ -1640,21 +1652,22 @@ class Article implements Page {
);
// @todo FIXME: i18n issue/patchwork message
- $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' .
- wfMessage( 'historywarning' )->numParams( $revisions )->parse() .
- wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title,
- wfMessage( 'history' )->escaped(),
- array( 'rel' => 'archives' ),
+ $context->getOutput()->addHTML(
+ '<strong class="mw-delete-warning-revisions">' .
+ $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
+ $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
+ $context->msg( 'history' )->escaped(),
+ array(),
array( 'action' => 'history' ) ) .
'</strong>'
);
if ( $title->isBigDeletion() ) {
global $wgDeleteRevisionsLimit;
- $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
+ $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
array(
'delete-warning-toobig',
- $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
+ $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
)
);
}
@@ -1672,7 +1685,9 @@ class Article implements Page {
wfDebug( "Article::confirmDelete\n" );
$title = $this->getTitle();
- $outputPage = $this->getContext()->getOutput();
+ $ctx = $this->getContext();
+ $outputPage = $ctx->getOutput();
+ $useMediaWikiUIEverywhere = $ctx->getConfig()->get( 'UseMediaWikiUIEverywhere' );
$outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
$outputPage->addBacklinkSubtitle( $title );
$outputPage->setRobotPolicy( 'noindex,nofollow' );
@@ -1683,80 +1698,72 @@ class Article implements Page {
}
$outputPage->addWikiMsg( 'confirmdeletetext' );
- wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) );
+ Hooks::run( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) );
$user = $this->getContext()->getUser();
if ( $user->isAllowed( 'suppressrevision' ) ) {
- $suppress = "<tr id=\"wpDeleteSuppressRow\">
- <td></td>
- <td class='mw-input'><strong>" .
+ $suppress = Html::openElement( 'div', array( 'id' => 'wpDeleteSuppressRow' ) ) .
+ "<strong>" .
Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
- "</strong></td>
- </tr>";
+ "</strong>" .
+ Html::closeElement( 'div' );
} else {
$suppress = '';
}
$checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
- $form = Xml::openElement( 'form', array( 'method' => 'post',
+ $form = Html::openElement( 'form', array( 'method' => 'post',
'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' ) ) .
- "<tr id=\"wpDeleteReasonListRow\">
- <td class='mw-label'>" .
- Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::listDropDown(
- 'wpDeleteReasonList',
- wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
- wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
- '',
- 'wpReasonDropDown',
- 1
- ) .
- "</td>
- </tr>
- <tr id=\"wpDeleteReasonRow\">
- <td class='mw-label'>" .
- Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
- "</td>
- <td class='mw-input'>" .
- Html::input( 'wpReason', $reason, 'text', array(
- 'size' => '60',
- 'maxlength' => '255',
- 'tabindex' => '2',
- 'id' => 'wpReason',
- 'autofocus'
- ) ) .
- "</td>
- </tr>";
+ Html::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
+ Html::element( 'legend', null, wfMessage( 'delete-legend' )->text() ) .
+ Html::openElement( 'div', array( 'id' => 'mw-deleteconfirm-table' ) ) .
+ Html::openElement( 'div', array( 'id' => 'wpDeleteReasonListRow' ) ) .
+ Html::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
+ '&nbsp;' .
+ Xml::listDropDown(
+ 'wpDeleteReasonList',
+ wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
+ wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
+ '',
+ 'wpReasonDropDown',
+ 1
+ ) .
+ Html::closeElement( 'div' ) .
+ Html::openElement( 'div', array( 'id' => 'wpDeleteReasonRow' ) ) .
+ Html::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
+ '&nbsp;' .
+ Html::input( 'wpReason', $reason, 'text', array(
+ 'size' => '60',
+ 'maxlength' => '255',
+ 'tabindex' => '2',
+ 'id' => 'wpReason',
+ 'class' => 'mw-ui-input-inline',
+ 'autofocus'
+ ) ) .
+ Html::closeElement( 'div' );
# Disallow watching if user is not logged in
if ( $user->isLoggedIn() ) {
- $form .= "
- <tr>
- <td></td>
- <td class='mw-input'>" .
+ $form .=
Xml::checkLabel( wfMessage( 'watchthis' )->text(),
- 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
- "</td>
- </tr>";
+ 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) );
}
- $form .= "
- $suppress
- <tr>
- <td></td>
- <td class='mw-submit'>" .
+ $form .=
+ Html::openElement( 'div' ) .
+ $suppress .
Xml::submitButton( wfMessage( 'deletepage' )->text(),
- array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
- "</td>
- </tr>" .
- Xml::closeElement( 'table' ) .
+ array(
+ 'name' => 'wpConfirmB',
+ 'id' => 'wpConfirmB',
+ 'tabindex' => '5',
+ 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button mw-ui-destructive' : '',
+ )
+ ) .
+ Html::closeElement( 'div' ) .
+ Html::closeElement( 'div' ) .
Xml::closeElement( 'fieldset' ) .
Html::hidden(
'wpEditToken',
@@ -1801,6 +1808,9 @@ class Article implements Page {
$loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
$outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
+
+ Hooks::run( 'ArticleDeleteAfterSuccess', array( $this->getTitle(), $outputPage ) );
+
$outputPage->returnToMain( false );
} else {
$outputPage->setPageTitle(
@@ -1873,7 +1883,7 @@ class Article implements Page {
&& !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
// Extension may have reason to disable file caching on some pages.
if ( $cacheable ) {
- $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
+ $cacheable = Hooks::run( 'IsFileCacheable', array( &$this ) );
}
}
diff --git a/includes/page/CategoryPage.php b/includes/page/CategoryPage.php
index 9abc6a89..caebcd7d 100644
--- a/includes/page/CategoryPage.php
+++ b/includes/page/CategoryPage.php
@@ -61,7 +61,7 @@ class CategoryPage extends Article {
return;
}
- if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) {
+ if ( !Hooks::run( 'CategoryPageView', array( &$this ) ) ) {
return;
}
@@ -113,6 +113,8 @@ class CategoryPage extends Article {
$until,
$reqArray
);
- $this->getContext()->getOutput()->addHTML( $viewer->getHTML() );
+ $out = $this->getContext()->getOutput();
+ $out->addHTML( $viewer->getHTML() );
+ $this->addHelpLink( 'Help:Categories' );
}
}
diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php
index d06c8191..8f635cfa 100644
--- a/includes/page/ImagePage.php
+++ b/includes/page/ImagePage.php
@@ -76,7 +76,7 @@ class ImagePage extends Article {
$this->fileLoaded = true;
$this->displayImg = $img = false;
- wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) );
+ Hooks::run( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) );
if ( !$img ) { // not set by hook?
$img = wfFindFile( $this->getTitle() );
if ( !$img ) {
@@ -140,7 +140,7 @@ class ImagePage extends Article {
if ( $wgShowEXIF && $this->displayImg->exists() ) {
// @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
- $formattedMetadata = $this->displayImg->formatMetadata();
+ $formattedMetadata = $this->displayImg->formatMetadata( $this->getContext() );
$showmeta = $formattedMetadata !== false;
} else {
$showmeta = false;
@@ -175,7 +175,7 @@ class ImagePage extends Article {
# Show shared description, if needed
if ( $this->mExtraDescription ) {
- $fol = wfMessage( 'shareddescriptionfollows' );
+ $fol = $this->getContext()->msg( 'shareddescriptionfollows' );
if ( !$fol->isDisabled() ) {
$out->addWikiText( $fol->plain() );
}
@@ -188,7 +188,7 @@ class ImagePage extends Article {
$out->addHTML( Xml::element( 'h2',
array( 'id' => 'filelinks' ),
- wfMessage( 'imagelinks' )->text() ) . "\n" );
+ $this->getContext()->msg( 'imagelinks' )->text() ) . "\n" );
$this->imageDupes();
# @todo FIXME: For some freaky reason, we can't redirect to foreign images.
# Yet we return metadata about the target. Definitely an issue in the FileRepo
@@ -196,7 +196,7 @@ class ImagePage extends Article {
# Allow extensions to add something after the image links
$html = '';
- wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) );
+ Hooks::run( 'ImagePageAfterImageLinks', array( $this, &$html ) );
if ( $html ) {
$out->addHTML( $html );
}
@@ -205,7 +205,7 @@ class ImagePage extends Article {
$out->addHTML( Xml::element(
'h2',
array( 'id' => 'metadata' ),
- wfMessage( 'metadata' )->text() ) . "\n" );
+ $this->getContext()->msg( 'metadata' )->text() ) . "\n" );
$out->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
$out->addModules( array( 'mediawiki.action.view.metadata' ) );
}
@@ -237,16 +237,17 @@ class ImagePage extends Article {
*/
protected function showTOC( $metadata ) {
$r = array(
- '<li><a href="#file">' . wfMessage( 'file-anchor-link' )->escaped() . '</a></li>',
- '<li><a href="#filehistory">' . wfMessage( 'filehist' )->escaped() . '</a></li>',
- '<li><a href="#filelinks">' . wfMessage( 'imagelinks' )->escaped() . '</a></li>',
+ '<li><a href="#file">' . $this->getContext()->msg( 'file-anchor-link' )->escaped() . '</a></li>',
+ '<li><a href="#filehistory">' . $this->getContext()->msg( 'filehist' )->escaped() . '</a></li>',
+ '<li><a href="#filelinks">' . $this->getContext()->msg( 'imagelinks' )->escaped() . '</a></li>',
);
+
+ Hooks::run( 'ImagePageShowTOC', array( $this, &$r ) );
+
if ( $metadata ) {
- $r[] = '<li><a href="#metadata">' . wfMessage( 'metadata' )->escaped() . '</a></li>';
+ $r[] = '<li><a href="#metadata">' . $this->getContext()->msg( 'metadata' )->escaped() . '</a></li>';
}
- wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) );
-
return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
}
@@ -260,7 +261,7 @@ class ImagePage extends Article {
*/
protected function makeMetadataTable( $metadata ) {
$r = "<div class=\"mw-imagepage-section-metadata\">";
- $r .= wfMessage( 'metadata-help' )->plain();
+ $r .= $this->getContext()->msg( 'metadata-help' )->plain();
$r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
foreach ( $metadata as $type => $stuff ) {
foreach ( $stuff as $v ) {
@@ -336,19 +337,19 @@ class ImagePage extends Article {
$filename = wfEscapeWikiText( $this->displayImg->getName() );
$linktext = $filename;
- wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) );
+ Hooks::run( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) );
if ( $this->displayImg->allowInlineDisplay() ) {
# image
# "Download high res version" link below the image
- # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig,
+ # $msgsize = $this->getContext()->msg( '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 || $this->displayImg->isVectorized() ) {
list( $width, $height ) = $this->getDisplayWidthHeight(
$maxWidth, $maxHeight, $width, $height
);
- $linktext = wfMessage( 'show-big-image' )->escaped();
+ $linktext = $this->getContext()->msg( 'show-big-image' )->escaped();
$thumbSizes = $this->getThumbSizes( $width, $height, $width_orig, $height_orig );
# Generate thumbnails or thumbnail links as needed...
@@ -377,14 +378,14 @@ class ImagePage extends Article {
$msgsmall = '';
$sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
if ( $sizeLinkBigImagePreview ) {
- $msgsmall .= wfMessage( 'show-big-image-preview' )->
+ $msgsmall .= $this->getContext()->msg( 'show-big-image-preview' )->
rawParams( $sizeLinkBigImagePreview )->
parse();
}
if ( count( $otherSizes ) ) {
$msgsmall .= ' ' .
Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ),
- wfMessage( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )->
+ $this->getContext()->msg( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )->
params( count( $otherSizes ) )->parse()
);
}
@@ -394,7 +395,7 @@ class ImagePage extends Article {
$msgsmall = '';
} else {
# Image is small enough to show full size on image page
- $msgsmall = wfMessage( 'file-nohires' )->parse();
+ $msgsmall = $this->getContext()->msg( 'file-nohires' )->parse();
}
$params['width'] = $width;
@@ -428,7 +429,7 @@ class ImagePage extends Article {
$count = $this->displayImg->pageCount();
if ( $page > 1 ) {
- $label = $out->parse( wfMessage( 'imgmultipageprev' )->text(), false );
+ $label = $out->parse( $this->getContext()->msg( 'imgmultipageprev' )->text(), false );
// on the client side, this link is generated in ajaxifyPageNavigation()
// in the mediawiki.page.image.pagination module
$link = Linker::linkKnown(
@@ -450,7 +451,7 @@ class ImagePage extends Article {
}
if ( $page < $count ) {
- $label = wfMessage( 'imgmultipagenext' )->text();
+ $label = $this->getContext()->msg( 'imgmultipagenext' )->text();
$link = Linker::linkKnown(
$this->getTitle(),
$label,
@@ -487,8 +488,8 @@ class ImagePage extends Article {
'</td><td><div class="multipageimagenavbox">' .
Xml::openElement( 'form', $formParams ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
- wfMessage( 'imgmultigoto' )->rawParams( $select )->parse() .
- Xml::submitButton( wfMessage( 'imgmultigo' )->text() ) .
+ $this->getContext()->msg( 'imgmultigoto' )->rawParams( $select )->parse() .
+ Xml::submitButton( $this->getContext()->msg( 'imgmultigo' )->text() ) .
Xml::closeElement( 'form' ) .
"<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
);
@@ -502,12 +503,28 @@ class ImagePage extends Article {
"</div>\n" );
}
- $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text();
+ $longDesc = $this->getContext()->msg( 'parentheses', $this->displayImg->getLongDesc() )->text();
+
+ $handler = $this->displayImg->getHandler();
+
+ // If this is a filetype with potential issues, warn the user.
+ if ( $handler ) {
+ $warningConfig = $handler->getWarningConfig( $this->displayImg );
+
+ if ( $warningConfig !== null ) {
+ // The warning will be displayed via CSS and JavaScript.
+ // We just need to tell the client side what message to use.
+ $output = $this->getContext()->getOutput();
+ $output->addJsConfigVars( 'wgFileWarning', $warningConfig );
+ $output->addModules( $warningConfig['module'] );
+ $output->addModules( 'mediawiki.filewarning' );
+ }
+ }
$medialink = "[[Media:$filename|$linktext]]";
if ( !$this->displayImg->isSafeFile() ) {
- $warning = wfMessage( 'mediawarning' )->plain();
+ $warning = $this->getContext()->msg( 'mediawarning' )->plain();
// dirmark is needed here to separate the file name, which
// most likely ends in Latin characters, from the description,
// which may begin with the file type. In RTL environment
@@ -619,7 +636,7 @@ EOT
return Html::rawElement( 'a', array(
'href' => $thumbnail->getUrl(),
'class' => 'mw-thumbnail-link'
- ), wfMessage( 'show-big-image-size' )->numParams(
+ ), $this->getContext()->msg( 'show-big-image-size' )->numParams(
$thumbnail->getWidth(), $thumbnail->getHeight()
)->parse() );
} else {
@@ -645,9 +662,9 @@ EOT
$wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
$repo = $this->mPage->getFile()->getRepo()->getDisplayName();
- if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) {
+ if ( $descUrl && $descText && $this->getContext()->msg( 'sharedupload-desc-here' )->plain() !== '-' ) {
$out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
- } elseif ( $descUrl && wfMessage( 'sharedupload-desc-there' )->plain() !== '-' ) {
+ } elseif ( $descUrl && $this->getContext()->msg( 'sharedupload-desc-there' )->plain() !== '-' ) {
$out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) );
} else {
$out->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ );
@@ -694,7 +711,7 @@ EOT
) {
$ulink = Linker::makeExternalLink(
$this->getUploadUrl(),
- wfMessage( 'uploadnewversion-linktext' )->text()
+ $this->getContext()->msg( 'uploadnewversion-linktext' )->text()
);
$out->addHTML( "<li id=\"mw-imagepage-reupload-link\">"
. "<div class=\"plainlinks\">{$ulink}</div></li>\n" );
@@ -832,7 +849,7 @@ EOT
$liContents = $link;
} elseif ( count( $redirects[$element->page_title] ) === 0 ) {
# Redirect without usages
- $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse();
+ $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse();
} else {
# Redirect with usages
$li = '';
@@ -855,7 +872,7 @@ EOT
array( 'class' => 'mw-imagepage-redirectstofile' ),
$li
) . "\n";
- $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams(
+ $liContents = $this->getContext()->msg( 'linkstoimage-redirect' )->rawParams(
$link, $ul )->parse();
}
$out->addHTML( Html::rawElement(
@@ -901,7 +918,7 @@ EOT
} else {
$link = Linker::makeExternalLink( $file->getDescriptionUrl(),
$file->getTitle()->getPrefixedText() );
- $fromSrc = wfMessage( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text();
+ $fromSrc = $this->getContext()->msg( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text();
}
$out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
}
@@ -930,7 +947,7 @@ EOT
*/
function showError( $description ) {
$out = $this->getContext()->getOutput();
- $out->setPageTitle( wfMessage( 'internalerror' ) );
+ $out->setPageTitle( $this->getContext()->msg( 'internalerror' ) );
$out->setRobotPolicy( 'noindex,nofollow' );
$out->setArticleRelated( false );
$out->enableClientCache( false );
@@ -1008,7 +1025,7 @@ EOT
$code = wfBCP47( $lang );
$name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
if ( $name !== '' ) {
- $display = wfMessage( 'img-lang-opt', $code, $name )->text();
+ $display = $this->getContext()->msg( 'img-lang-opt', $code, $name )->text();
} else {
$display = $code;
}
@@ -1024,7 +1041,7 @@ EOT
// 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(),
+ $this->getContext()->msg( 'img-lang-default' )->text(),
$defaultLang,
$defaultLang === $curLang
) . $opts;
@@ -1032,7 +1049,7 @@ EOT
if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
$name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
if ( $name !== '' ) {
- $display = wfMessage( 'img-lang-opt', $curLang, $name )->text();
+ $display = $this->getContext()->msg( 'img-lang-opt', $curLang, $name )->text();
} else {
$display = $curLang;
}
@@ -1044,9 +1061,9 @@ EOT
array( 'id' => 'mw-imglangselector', 'name' => 'lang' ),
$opts
);
- $submit = Xml::submitButton( wfMessage( 'img-lang-go' )->text() );
+ $submit = Xml::submitButton( $this->getContext()->msg( 'img-lang-go' )->text() );
- $formContents = wfMessage( 'img-lang-info' )->rawParams( $select, $submit )->parse()
+ $formContents = $this->getContext()->msg( 'img-lang-info' )->rawParams( $select, $submit )->parse()
. Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
$langSelectLine = Html::rawElement( 'div', array( 'id' => 'mw-imglangselector-line' ),
@@ -1199,9 +1216,9 @@ class ImageHistoryList extends ContextSource {
. $this->msg( 'filehist-help' )->parseAsBlock()
. $navLinks . "\n"
. Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n"
- . '<tr><td></td>'
+ . '<tr><th></th>'
. ( $this->current->isLocal()
- && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' )
+ && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<th></th>' : '' )
. '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>'
. ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' )
. '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>'
@@ -1364,7 +1381,6 @@ class ImageHistoryList extends ContextSource {
} else {
if ( $local ) {
$row .= Linker::userLink( $userId, $userText );
- $row .= $this->msg( 'word-separator' )->escaped();
$row .= '<span style="white-space: nowrap;">';
$row .= Linker::userToolLinks( $userId, $userText );
$row .= '</span>';
@@ -1384,7 +1400,7 @@ class ImageHistoryList extends ContextSource {
}
$rowClass = null;
- wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
+ Hooks::run( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
$classAttr = $rowClass ? " class='$rowClass'" : '';
return "<tr{$classAttr}>{$row}</tr>\n";
diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php
index 9ade16e5..7c789249 100644
--- a/includes/page/WikiPage.php
+++ b/includes/page/WikiPage.php
@@ -31,8 +31,6 @@ interface Page {
*
* Some fields are public only for backwards-compatibility. Use accessors.
* In the past, this class was part of Article.php and everything was public.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class WikiPage implements Page, IDBAccessObject {
// Constants for $mDataLoadedFrom and related
@@ -50,7 +48,7 @@ class WikiPage implements Page, IDBAccessObject {
public $mLatest = false; // !< Integer (false means "not loaded")
/**@}}*/
- /** @var stdclass Map of cache fields (text, parser output, ect) for a proposed/new edit */
+ /** @var stdClass Map of cache fields (text, parser output, ect) for a proposed/new edit */
public $mPreparedEdit = false;
/**
@@ -89,11 +87,6 @@ class WikiPage implements Page, IDBAccessObject {
protected $mLinksUpdated = '19700101000000';
/**
- * @var int|null
- */
- protected $mCounter = null;
-
- /**
* Constructor and clear the article
* @param Title $title Reference to a Title object.
*/
@@ -247,7 +240,6 @@ class WikiPage implements Page, IDBAccessObject {
*/
protected function clearCacheFields() {
$this->mId = null;
- $this->mCounter = null;
$this->mRedirectTarget = null; // Title object if set
$this->mLastRevision = null; // Latest revision
$this->mTouched = '19700101000000';
@@ -284,7 +276,6 @@ class WikiPage implements Page, IDBAccessObject {
'page_namespace',
'page_title',
'page_restrictions',
- 'page_counter',
'page_is_redirect',
'page_is_new',
'page_random',
@@ -315,11 +306,11 @@ class WikiPage implements Page, IDBAccessObject {
protected function pageData( $dbr, $conditions, $options = array() ) {
$fields = self::selectFields();
- wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
+ Hooks::run( 'ArticlePageDataBefore', array( &$this, &$fields ) );
$row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
- wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
+ Hooks::run( 'ArticlePageDataAfter', array( &$this, &$row ) );
return $row;
}
@@ -352,8 +343,7 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * Set the general counter, title etc data loaded from
- * some source.
+ * Load the object from a given source by title
*
* @param object|string|int $from One of the following:
* - A DB query result object.
@@ -377,14 +367,12 @@ class WikiPage implements Page, IDBAccessObject {
$data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
} elseif ( $from === self::READ_NORMAL ) {
$data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
- // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
- // Note that DB also stores the master position in the session and checks it.
- $touched = $this->getCachedLastEditTime();
- if ( $touched ) { // key set
- if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
- $from = self::READ_LATEST;
- $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
- }
+ if ( !$data
+ && wfGetLB()->getServerCount() > 1
+ && wfGetLB()->hasOrMadeRecentMasterChanges()
+ ) {
+ $from = self::READ_LATEST;
+ $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
}
} else {
// No idea from where the caller got this data, assume slave database.
@@ -403,7 +391,7 @@ class WikiPage implements Page, IDBAccessObject {
* @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
- * - "forupdate" or WikiPage::READ_LOCKING if the data comes from from
+ * - "forupdate" or WikiPage::READ_LOCKING if the data comes from
* the master DB using SELECT FOR UPDATE
*/
public function loadFromRow( $data, $from ) {
@@ -419,7 +407,6 @@ class WikiPage implements Page, IDBAccessObject {
$this->mTitle->loadRestrictions( $data->page_restrictions );
$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 );
@@ -477,17 +464,6 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * @return int The view count for the page
- */
- public function getCount() {
- if ( !$this->mDataLoaded ) {
- $this->loadPageData();
- }
-
- return $this->mCounter;
- }
-
- /**
* Tests if the article content represents a redirect
*
* @return bool
@@ -577,7 +553,6 @@ class WikiPage implements Page, IDBAccessObject {
* @return Revision|null
*/
public function getOldestRevision() {
- wfProfileIn( __METHOD__ );
// Try using the slave database first, then try the master
$continue = 2;
@@ -608,7 +583,6 @@ class WikiPage implements Page, IDBAccessObject {
}
}
- wfProfileOut( __METHOD__ );
return $row ? Revision::newFromRow( $row ) : null;
}
@@ -626,13 +600,23 @@ class WikiPage implements Page, IDBAccessObject {
return; // page doesn't exist or is missing page_latest info
}
- // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the
- // latest changes committed. This is true even within REPEATABLE-READ transactions, where
- // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to
- // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row
- // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT.
- // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
- $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0;
+ if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
+ // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always
+ // includes the latest changes committed. This is true even within REPEATABLE-READ
+ // transactions, where S1 normally only sees changes committed before the first S1
+ // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
+ // may not find it since a page row UPDATE and revision row INSERT by S2 may have
+ // happened after the first S1 SELECT.
+ // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
+ $flags = Revision::READ_LOCKING;
+ } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
+ // Bug T93976: if page_latest was loaded from the master, fetch the
+ // revision from there as well, as it may not exist yet on a slave DB.
+ // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
+ $flags = Revision::READ_LATEST;
+ } else {
+ $flags = 0;
+ }
$revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
if ( $revision ) { // sanity
$this->setLastEdit( $revision );
@@ -825,29 +809,6 @@ class WikiPage implements Page, IDBAccessObject {
}
/**
- * Get the cached timestamp for the last time the page changed.
- * This is only used to help handle slave lag by comparing to page_touched.
- * @return string MW timestamp
- */
- protected function getCachedLastEditTime() {
- global $wgMemc;
- $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
- return $wgMemc->get( $key );
- }
-
- /**
- * 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 string $timestamp
- * @return void
- */
- public function setCachedLastEditTime( $timestamp ) {
- global $wgMemc;
- $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
- $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 );
- }
-
- /**
* Determine whether a page would be suitable for being counted as an
* article in the site_stats table based on the title & its content
*
@@ -995,7 +956,7 @@ class WikiPage implements Page, IDBAccessObject {
$source = $this->mTitle->getFullURL( 'redirect=no' );
return $rt->getFullURL( array( 'rdfrom' => $source ) );
} else {
- // External pages pages without "local" bit set are not valid
+ // External pages without "local" bit set are not valid
// redirect targets
return false;
}
@@ -1075,7 +1036,6 @@ class WikiPage implements Page, IDBAccessObject {
* @return array Array of authors, duplicates not removed
*/
public function getLastNAuthors( $num, $revLatest = 0 ) {
- wfProfileIn( __METHOD__ );
// First try the slave
// If that doesn't have the latest revision, try the master
$continue = 2;
@@ -1096,7 +1056,6 @@ class WikiPage implements Page, IDBAccessObject {
);
if ( !$res ) {
- wfProfileOut( __METHOD__ );
return array();
}
@@ -1116,7 +1075,6 @@ class WikiPage implements Page, IDBAccessObject {
$authors[] = $row->rev_user_text;
}
- wfProfileOut( __METHOD__ );
return $authors;
}
@@ -1149,7 +1107,6 @@ class WikiPage implements Page, IDBAccessObject {
* @return ParserOutput|bool ParserOutput or false if the revision was not found
*/
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
- wfProfileIn( __METHOD__ );
$useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
@@ -1160,7 +1117,6 @@ class WikiPage implements Page, IDBAccessObject {
if ( $useParserCache ) {
$parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
if ( $parserOutput !== false ) {
- wfProfileOut( __METHOD__ );
return $parserOutput;
}
}
@@ -1172,8 +1128,6 @@ class WikiPage implements Page, IDBAccessObject {
$pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
$pool->execute();
- wfProfileOut( __METHOD__ );
-
return $pool->getParserOutput();
}
@@ -1183,17 +1137,11 @@ class WikiPage implements Page, IDBAccessObject {
* @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
*/
public function doViewUpdates( User $user, $oldid = 0 ) {
- global $wgDisableCounters;
if ( wfReadOnly() ) {
return;
}
- // Don't update page view counters on views from bot users (bug 14044)
- if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->exists() ) {
- DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
- DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
- }
-
+ Hooks::run( 'PageViewUpdates', array( $this, $user ) );
// Update newtalk / watchlist notification status
$user->clearNotification( $this->mTitle, $oldid );
}
@@ -1205,7 +1153,7 @@ class WikiPage implements Page, IDBAccessObject {
public function doPurge() {
global $wgUseSquid;
- if ( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
+ if ( !Hooks::run( 'ArticlePurge', array( &$this ) ) ) {
return false;
}
@@ -1255,14 +1203,12 @@ class WikiPage implements Page, IDBAccessObject {
* @return int The newly created page_id key, or false if the title already existed
*/
public function insertOn( $dbw ) {
- wfProfileIn( __METHOD__ );
$page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
$dbw->insert( 'page', array(
'page_id' => $page_id,
'page_namespace' => $this->mTitle->getNamespace(),
'page_title' => $this->mTitle->getDBkey(),
- 'page_counter' => 0,
'page_restrictions' => '',
'page_is_redirect' => 0, // Will set this shortly...
'page_is_new' => 1,
@@ -1279,7 +1225,6 @@ class WikiPage implements Page, IDBAccessObject {
$this->mId = $newid;
$this->mTitle->resetArticleID( $newid );
}
- wfProfileOut( __METHOD__ );
return $affected ? $newid : false;
}
@@ -1302,7 +1247,12 @@ class WikiPage implements Page, IDBAccessObject {
) {
global $wgContentHandlerUseDB;
- wfProfileIn( __METHOD__ );
+ // Assertion to try to catch T92046
+ if ( (int)$revision->getId() === 0 ) {
+ throw new InvalidArgumentException(
+ __METHOD__ . ': Revision has ID ' . var_export( $revision->getId(), 1 )
+ );
+ }
$content = $revision->getContent();
$len = $content ? $content->getSize() : 0;
@@ -1337,7 +1287,6 @@ class WikiPage implements Page, IDBAccessObject {
if ( $result ) {
$this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
$this->setLastEdit( $revision );
- $this->setCachedLastEditTime( $now );
$this->mLatest = $revision->getId();
$this->mIsRedirect = (bool)$rt;
// Update the LinkCache.
@@ -1345,7 +1294,6 @@ class WikiPage implements Page, IDBAccessObject {
$this->mLatest, $revision->getContentModel() );
}
- wfProfileOut( __METHOD__ );
return $result;
}
@@ -1370,7 +1318,6 @@ class WikiPage implements Page, IDBAccessObject {
return true;
}
- wfProfileIn( __METHOD__ );
if ( $isRedirect ) {
$this->insertRedirectEntry( $redirectTitle );
} else {
@@ -1382,7 +1329,6 @@ class WikiPage implements Page, IDBAccessObject {
if ( $this->getTitle()->getNamespace() == NS_FILE ) {
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
}
- wfProfileOut( __METHOD__ );
return ( $dbw->affectedRows() != 0 );
}
@@ -1398,7 +1344,6 @@ class WikiPage implements Page, IDBAccessObject {
* @return bool
*/
public function updateIfNewerOn( $dbw, $revision ) {
- wfProfileIn( __METHOD__ );
$row = $dbw->selectRow(
array( 'revision', 'page' ),
@@ -1410,7 +1355,6 @@ class WikiPage implements Page, IDBAccessObject {
if ( $row ) {
if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
- wfProfileOut( __METHOD__ );
return false;
}
$prev = $row->rev_id;
@@ -1423,7 +1367,6 @@ class WikiPage implements Page, IDBAccessObject {
$ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -1542,18 +1485,26 @@ class WikiPage implements Page, IDBAccessObject {
*/
public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
$edittime = null ) {
- wfProfileIn( __METHOD__ );
$baseRevId = null;
if ( $edittime && $sectionId !== 'new' ) {
- $dbw = wfGetDB( DB_MASTER );
- $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
+ $dbr = wfGetDB( DB_SLAVE );
+ $rev = Revision::loadFromTimestamp( $dbr, $this->mTitle, $edittime );
+ // Try the master if this thread may have just added it.
+ // This could be abstracted into a Revision method, but we don't want
+ // to encourage loading of revisions by timestamp.
+ if ( !$rev
+ && wfGetLB()->getServerCount() > 1
+ && wfGetLB()->hasOrMadeRecentMasterChanges()
+ ) {
+ $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 );
}
@@ -1573,14 +1524,12 @@ class WikiPage implements Page, IDBAccessObject {
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() );
}
@@ -1589,14 +1538,10 @@ class WikiPage implements Page, IDBAccessObject {
if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
$oldContent = $this->getContent();
} else {
- // TODO: try DB_SLAVE first
- $dbw = wfGetDB( DB_MASTER );
- $rev = Revision::loadFromId( $dbw, $baseRevId );
-
+ $rev = Revision::newFromId( $baseRevId );
if ( !$rev ) {
wfDebug( __METHOD__ . " asked for bogus section (page: " .
$this->getId() . "; section: $sectionId)\n" );
- wfProfileOut( __METHOD__ );
return null;
}
@@ -1605,14 +1550,12 @@ class WikiPage implements Page, IDBAccessObject {
if ( !$oldContent ) {
wfDebug( __METHOD__ . ": no page text\n" );
- wfProfileOut( __METHOD__ );
return null;
}
$newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
}
- wfProfileOut( __METHOD__ );
return $newContent;
}
@@ -1662,7 +1605,9 @@ class WikiPage implements Page, IDBAccessObject {
* 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 bool|int $baseRevId The revision ID this edit was based off, if any.
+ * This is not the parent revision ID, rather the revision ID for older
+ * content used as the source for a rollback, for example.
* @param User $user The user doing the edit
*
* @throws MWException
@@ -1722,9 +1667,11 @@ class WikiPage implements Page, IDBAccessObject {
* 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 bool|int $baseRevId The revision ID this edit was based off, if any.
+ * This is not the parent revision ID, rather the revision ID for older
+ * content used as the source for a rollback, for example.
* @param User $user The user doing the edit
- * @param string $serialisation_format Format for storing the content in the
+ * @param string $serialFormat Format for storing the content in the
* database.
*
* @throws MWException
@@ -1745,7 +1692,7 @@ class WikiPage implements Page, IDBAccessObject {
* @since 1.21
*/
public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
- User $user = null, $serialisation_format = null
+ User $user = null, $serialFormat = null
) {
global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
@@ -1754,10 +1701,7 @@ class WikiPage implements Page, IDBAccessObject {
throw new MWException( 'Something is trying to edit an article with an empty title' );
}
- wfProfileIn( __METHOD__ );
-
if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
- wfProfileOut( __METHOD__ );
return Status::newFatal( 'content-not-allowed-here',
ContentHandler::getLocalizedName( $content->getModel() ),
$this->getTitle()->getPrefixedText() );
@@ -1777,7 +1721,7 @@ class WikiPage implements Page, IDBAccessObject {
$hook_args = array( &$this, &$user, &$content, &$summary,
$flags & EDIT_MINOR, null, null, &$flags, &$status );
- if ( !wfRunHooks( 'PageContentSave', $hook_args )
+ if ( !Hooks::run( 'PageContentSave', $hook_args )
|| !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
@@ -1786,7 +1730,6 @@ class WikiPage implements Page, IDBAccessObject {
$status->fatal( 'edit-hook-aborted' );
}
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1811,7 +1754,7 @@ class WikiPage implements Page, IDBAccessObject {
$summary = $handler->getAutosummary( $old_content, $content, $flags );
}
- $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
+ $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialFormat );
$serialized = $editInfo->pst;
/**
@@ -1833,11 +1776,9 @@ class WikiPage implements Page, IDBAccessObject {
wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
$status->fatal( 'edit-gone-missing' );
- wfProfileOut( __METHOD__ );
return $status;
} elseif ( !$old_content ) {
// Sanity check for bug 37225
- wfProfileOut( __METHOD__ );
throw new MWException( "Could not find text for current revision {$oldid}." );
}
@@ -1853,27 +1794,21 @@ class WikiPage implements Page, IDBAccessObject {
'user_text' => $user->getName(),
'timestamp' => $now,
'content_model' => $content->getModel(),
- 'content_format' => $serialisation_format,
+ 'content_format' => $serialFormat,
) ); // XXX: pass content object?!
$changed = !$content->equals( $old_content );
if ( $changed ) {
- if ( !$content->isValid() ) {
- wfProfileOut( __METHOD__ );
- throw new MWException( "New content failed validity check!" );
- }
-
$dbw->begin( __METHOD__ );
try {
- $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
if ( !$status->isOK() ) {
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return $status;
}
$revisionId = $revision->insertOn( $dbw );
@@ -1889,11 +1824,10 @@ class WikiPage implements Page, IDBAccessObject {
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return $status;
}
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
// Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
// Mark as patrolled if the user can do so
@@ -1911,7 +1845,7 @@ class WikiPage implements Page, IDBAccessObject {
}
}
$user->incEditCount();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$dbw->rollback( __METHOD__ );
// Question: Would it perhaps be better if this method turned all
// exceptions into $status's?
@@ -1948,13 +1882,12 @@ class WikiPage implements Page, IDBAccessObject {
$dbw->begin( __METHOD__ );
try {
- $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
if ( !$status->isOK() ) {
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1968,7 +1901,6 @@ class WikiPage implements Page, IDBAccessObject {
$dbw->rollback( __METHOD__ );
$status->fatal( 'edit-already-exists' );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -1984,7 +1916,7 @@ class WikiPage implements Page, IDBAccessObject {
'user_text' => $user->getName(),
'timestamp' => $now,
'content_model' => $content->getModel(),
- 'content_format' => $serialisation_format,
+ 'content_format' => $serialFormat,
) );
$revisionId = $revision->insertOn( $dbw );
@@ -1998,7 +1930,7 @@ class WikiPage implements Page, IDBAccessObject {
// Update the page record with revision data
$this->updateRevisionOn( $dbw, $revision, 0 );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
// Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
@@ -2016,7 +1948,7 @@ class WikiPage implements Page, IDBAccessObject {
}
$user->incEditCount();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$dbw->rollback( __METHOD__ );
throw $e;
}
@@ -2029,7 +1961,7 @@ class WikiPage implements Page, IDBAccessObject {
$flags & EDIT_MINOR, null, null, &$flags, $revision );
ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
- wfRunHooks( 'PageContentInsertComplete', $hook_args );
+ Hooks::run( 'PageContentInsertComplete', $hook_args );
}
// Do updates right now unless deferral was requested
@@ -2044,14 +1976,14 @@ class WikiPage implements Page, IDBAccessObject {
$flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
- wfRunHooks( 'PageContentSaveComplete', $hook_args );
+ Hooks::run( 'PageContentSaveComplete', $hook_args );
// Promote user to any groups they meet the criteria for
$dbw->onTransactionIdle( function () use ( $user ) {
$user->addAutopromoteOnceGroups( 'onEdit' );
+ $user->addAutopromoteOnceGroups( 'onView' ); // b/c
} );
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -2083,7 +2015,7 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Prepare text which is about to be saved.
- * Returns a stdclass with source, pst and output members
+ * Returns a stdClass with source, pst and output members
*
* @deprecated since 1.21: use prepareContentForEdit instead.
* @return object
@@ -2096,54 +2028,109 @@ class WikiPage implements Page, IDBAccessObject {
/**
* Prepare content which is about to be saved.
- * Returns a stdclass with source, pst and output members
+ * Returns a stdClass with source, pst and output members
*
* @param Content $content
- * @param int|null $revid
+ * @param Revision|int|null $revision Revision object. For backwards compatibility, a
+ * revision ID is also accepted, but this is deprecated.
* @param User|null $user
- * @param string|null $serialization_format
+ * @param string|null $serialFormat
+ * @param bool $useCache Check shared prepared edit cache
*
- * @return bool|object
+ * @return object
*
* @since 1.21
*/
- public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
- $serialization_format = null
+ public function prepareContentForEdit(
+ Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true
) {
- global $wgContLang, $wgUser;
+ global $wgContLang, $wgUser, $wgAjaxEditStash;
+
+ if ( is_object( $revision ) ) {
+ $revid = $revision->getId();
+ } else {
+ $revid = $revision;
+ // This code path is deprecated, and nothing is known to
+ // use it, so performance here shouldn't be a worry.
+ if ( $revid !== null ) {
+ $revision = Revision::newFromId( $revid, Revision::READ_LATEST );
+ } else {
+ $revision = null;
+ }
+ }
+
$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();
+ // Use a sane default for $serialFormat, see bug 57026
+ if ( $serialFormat === null ) {
+ $serialFormat = $content->getContentHandler()->getDefaultFormat();
}
if ( $this->mPreparedEdit
&& $this->mPreparedEdit->newContent
&& $this->mPreparedEdit->newContent->equals( $content )
&& $this->mPreparedEdit->revid == $revid
- && $this->mPreparedEdit->format == $serialization_format
+ && $this->mPreparedEdit->format == $serialFormat
// XXX: also check $user here?
) {
// Already prepared
return $this->mPreparedEdit;
}
+ // The edit may have already been prepared via api.php?action=stashedit
+ $cachedEdit = $useCache && $wgAjaxEditStash
+ ? ApiStashEdit::checkCache( $this->getTitle(), $content, $user )
+ : false;
+
$popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
- wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
+ Hooks::run( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
$edit = (object)array();
+ if ( $cachedEdit ) {
+ $edit->timestamp = $cachedEdit->timestamp;
+ } else {
+ $edit->timestamp = wfTimestampNow();
+ }
+ // @note: $cachedEdit is not used if the rev ID was referenced in the text
$edit->revid = $revid;
- $edit->timestamp = wfTimestampNow();
- $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
+ if ( $cachedEdit ) {
+ $edit->pstContent = $cachedEdit->pstContent;
+ } else {
+ $edit->pstContent = $content
+ ? $content->preSaveTransform( $this->mTitle, $user, $popts )
+ : null;
+ }
- $edit->format = $serialization_format;
+ $edit->format = $serialFormat;
$edit->popts = $this->makeParserOptions( 'canonical' );
- $edit->output = $edit->pstContent
- ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
- : null;
+ if ( $cachedEdit ) {
+ $edit->output = $cachedEdit->output;
+ } else {
+ if ( $revision ) {
+ // We get here if vary-revision is set. This means that this page references
+ // itself (such as via self-transclusion). In this case, we need to make sure
+ // that any such self-references refer to the newly-saved revision, and not
+ // to the previous one, which could otherwise happen due to slave lag.
+ $oldCallback = $edit->popts->setCurrentRevisionCallback(
+ function ( $title, $parser = false ) use ( $revision, &$oldCallback ) {
+ if ( $title->equals( $revision->getTitle() ) ) {
+ return $revision;
+ } else {
+ return call_user_func(
+ $oldCallback,
+ $title,
+ $parser
+ );
+ }
+ }
+ );
+ }
+ $edit->output = $edit->pstContent
+ ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
+ : null;
+ }
$edit->newContent = $content;
$edit->oldContent = $this->getContent( Revision::RAW );
@@ -2151,7 +2138,7 @@ class WikiPage implements Page, IDBAccessObject {
// NOTE: B/C for hooks! don't use these fields!
$edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
$edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
- $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
+ $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : '';
$this->mPreparedEdit = $edit;
return $edit;
@@ -2168,17 +2155,23 @@ class WikiPage implements Page, IDBAccessObject {
* @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):
+ * - moved: boolean, whether the page was moved (default false)
+ * - oldcountable: boolean, null, or string 'no-change' (default null):
* - boolean: whether the page was counted as an article before that
* revision, only used in changed is true and created is false
- * - null: don't change the article count
+ * - null: if created is false, don't update the article count; if created
+ * is true, do update the article count
+ * - 'no-change': don't update the article count, ever
*/
public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
global $wgEnableParserCache;
- wfProfileIn( __METHOD__ );
-
- $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
+ $options += array(
+ 'changed' => true,
+ 'created' => false,
+ 'moved' => false,
+ 'oldcountable' => null
+ );
$content = $revision->getContent();
// Parse the text
@@ -2186,7 +2179,7 @@ class WikiPage implements Page, IDBAccessObject {
// already pre-save transformed once.
if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
- $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
+ $editInfo = $this->prepareContentForEdit( $content, $revision, $user );
} else {
wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
$editInfo = $this->mPreparedEdit;
@@ -2208,18 +2201,18 @@ class WikiPage implements Page, IDBAccessObject {
DataUpdate::runUpdates( $updates );
}
- wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
+ Hooks::run( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
- if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
- if ( 0 == mt_rand( 0, 99 ) ) {
- // Flush old entries from the `recentchanges` table; we do this on
- // random requests so as to avoid an increase in writes for no good reason
- RecentChange::purgeExpiredChanges();
- }
+ if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
+ JobQueueGroup::singleton()->push( array(
+ // Flush old entries from the `recentchanges` table
+ RecentChangesUpdateJob::newPurgeJob(),
+ // Update the cached list of active users
+ RecentChangesUpdateJob::newCacheUpdateJob()
+ ) );
}
if ( !$this->exists() ) {
- wfProfileOut( __METHOD__ );
return;
}
@@ -2227,7 +2220,9 @@ class WikiPage implements Page, IDBAccessObject {
$title = $this->mTitle->getPrefixedDBkey();
$shortTitle = $this->mTitle->getDBkey();
- if ( !$options['changed'] ) {
+ if ( $options['oldcountable'] === 'no-change' ||
+ ( !$options['changed'] && !$options['moved'] )
+ ) {
$good = 0;
} elseif ( $options['created'] ) {
$good = (int)$this->isCountable( $editInfo );
@@ -2255,7 +2250,7 @@ class WikiPage implements Page, IDBAccessObject {
wfDebug( __METHOD__ . ": invalid username\n" );
} else {
// Allow extensions to prevent user notification when a new message is added to their talk page
- if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
+ if ( Hooks::run( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
if ( User::isIP( $shortTitle ) ) {
// An anonymous user
$recipient->setNewtalk( true, $revision );
@@ -2284,7 +2279,6 @@ class WikiPage implements Page, IDBAccessObject {
self::onArticleEdit( $this->mTitle );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -2315,14 +2309,13 @@ class WikiPage implements Page, IDBAccessObject {
* @param User $user The relevant user
* @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
+ * @param string $serialFormat Format for storing the content in the database
*/
public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
- $serialisation_format = null
+ $serialFormat = null
) {
- wfProfileIn( __METHOD__ );
- $serialized = $content->serialize( $serialisation_format );
+ $serialized = $content->serialize( $serialFormat );
$dbw = wfGetDB( DB_MASTER );
$revision = new Revision( array(
@@ -2338,9 +2331,8 @@ class WikiPage implements Page, IDBAccessObject {
$revision->insertOn( $dbw );
$this->updateRevisionOn( $dbw, $revision );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
- wfProfileOut( __METHOD__ );
}
/**
@@ -2437,7 +2429,7 @@ class WikiPage implements Page, IDBAccessObject {
$logRelationsField = null;
if ( $id ) { // Protection of existing page
- if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
+ if ( !Hooks::run( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
return Status::newGood();
}
@@ -2517,8 +2509,8 @@ class WikiPage implements Page, IDBAccessObject {
__METHOD__
);
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
- wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
+ Hooks::run( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
+ Hooks::run( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
} else { // Protection of non-existing page (also known as "title protection")
// Cascade protection is meaningless in this case
$cascade = false;
@@ -2780,7 +2772,7 @@ class WikiPage implements Page, IDBAccessObject {
}
$user = is_null( $user ) ? $wgUser : $user;
- if ( !wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
+ if ( !Hooks::run( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
if ( $status->isOK() ) {
// Hook aborted but didn't set a fatal status
$status->fatal( 'delete-hook-aborted' );
@@ -2896,7 +2888,7 @@ class WikiPage implements Page, IDBAccessObject {
$this->doDeleteUpdates( $id, $content );
- wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
+ Hooks::run( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
$status->value = $logid;
return $status;
}
@@ -3031,8 +3023,8 @@ class WikiPage implements Page, IDBAccessObject {
// Get the last edit not by this guy...
// Note: these may not be public values
- $user = intval( $current->getRawUser() );
- $user_text = $dbw->addQuotes( $current->getRawUserText() );
+ $user = intval( $current->getUser( Revision::RAW ) );
+ $user_text = $dbw->addQuotes( $current->getUserText( Revision::RAW ) );
$s = $dbw->selectRow( 'revision',
array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
array( 'rev_page' => $current->getPage(),
@@ -3075,7 +3067,7 @@ class WikiPage implements Page, IDBAccessObject {
}
// Generate the edit summary if necessary
- $target = Revision::newFromId( $s->rev_id );
+ $target = Revision::newFromId( $s->rev_id, Revision::READ_LATEST );
if ( empty( $summary ) ) {
if ( $from == '' ) { // no public user name
$summary = wfMessage( 'revertpage-nouser' );
@@ -3138,7 +3130,7 @@ class WikiPage implements Page, IDBAccessObject {
$revId = $status->value['revision']->getId();
- wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
+ Hooks::run( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
$resultDetails = array(
'summary' => $summary,
@@ -3161,13 +3153,9 @@ class WikiPage implements Page, IDBAccessObject {
*
* @param Title $title
*/
- public static function onArticleCreate( $title ) {
+ public static function onArticleCreate( Title $title ) {
// Update existence markers on article/talk tabs...
- if ( $title->isTalkPage() ) {
- $other = $title->getSubjectPage();
- } else {
- $other = $title->getTalkPage();
- }
+ $other = $title->getOtherPage();
$other->invalidateCache();
$other->purgeSquid();
@@ -3182,13 +3170,9 @@ class WikiPage implements Page, IDBAccessObject {
*
* @param Title $title
*/
- public static function onArticleDelete( $title ) {
+ public static function onArticleDelete( Title $title ) {
// Update existence markers on article/talk tabs...
- if ( $title->isTalkPage() ) {
- $other = $title->getSubjectPage();
- } else {
- $other = $title->getTalkPage();
- }
+ $other = $title->getOtherPage();
$other->invalidateCache();
$other->purgeSquid();
@@ -3227,10 +3211,8 @@ class WikiPage implements Page, IDBAccessObject {
* Purge caches on page update etc
*
* @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 ) {
+ public static function onArticleEdit( Title $title ) {
// Invalidate caches of articles which include this page
DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
@@ -3390,82 +3372,52 @@ class WikiPage implements Page, IDBAccessObject {
foreach ( $added as $catName ) {
$cat = Category::newFromName( $catName );
- wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $that ) );
+ Hooks::run( 'CategoryAfterPageAdded', array( $cat, $that ) );
}
foreach ( $deleted as $catName ) {
$cat = Category::newFromName( $catName );
- wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $that ) );
+ Hooks::run( 'CategoryAfterPageRemoved', array( $cat, $that ) );
}
}
);
}
/**
- * Updates cascading protections
+ * Opportunistically enqueue link update jobs given fresh parser output if useful
*
- * @param ParserOutput $parserOutput ParserOutput object for the current version
+ * @param ParserOutput $parserOutput Current version page output
+ * @since 1.25
*/
- public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
- if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
+ public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
+ if ( wfReadOnly() ) {
return;
}
- // 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 and images from imagelinks
- $id = $this->getId();
-
- $dbLinks = array();
-
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'templatelinks' ),
- array( 'tl_namespace', 'tl_title' ),
- array( 'tl_from' => $id ),
- __METHOD__
- );
-
- foreach ( $res as $row ) {
- $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
+ if ( !Hooks::run( 'OpportunisticLinksUpdate', array( $this, $this->mTitle, $parserOutput ) ) ) {
+ return;
}
- $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;
+ if ( $this->mTitle->areRestrictionsCascading() ) {
+ // If the page is cascade protecting, the links should really be up-to-date
+ $params = array( 'prioritize' => true );
+ } elseif ( $parserOutput->hasDynamicContent() ) {
+ // Assume the output contains time/random based magic words
+ $params = array();
+ } else {
+ // If the inclusions are deterministic, the edit-triggered link jobs are enough
+ return;
}
- // Get templates and images from parser output.
- $poLinks = array();
- foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
- foreach ( $templates as $dbk => $id ) {
- $poLinks["$ns:$dbk"] = true;
- }
- }
- foreach ( $parserOutput->getImages() as $dbk => $id ) {
- $poLinks[NS_FILE . ":$dbk"] = true;
+ // Check if the last link refresh was before page_touched
+ if ( $this->getLinksTimestamp() < $this->getTouched() ) {
+ JobQueueGroup::singleton()->push( EnqueueJob::newFromLocalJobs(
+ new JobSpecification( 'refreshLinks', $params, array(), $this->mTitle )
+ ) );
+ return;
}
- // Get the diff
- $links_diff = array_diff_key( $poLinks, $dbLinks );
-
- 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.
- $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
- $u->doUpdate();
- }
+ return;
}
/**
@@ -3548,7 +3500,7 @@ class WikiPage implements Page, IDBAccessObject {
$updates = $content->getDeletionUpdates( $this );
}
- wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
+ Hooks::run( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
return $updates;
}
}
diff --git a/includes/pager/IndexPager.php b/includes/pager/IndexPager.php
index ce6dc50c..7a5952f1 100644
--- a/includes/pager/IndexPager.php
+++ b/includes/pager/IndexPager.php
@@ -196,7 +196,7 @@ abstract class IndexPager extends ContextSource implements Pager {
public function doQuery() {
# Use the child class name for profiling
$fname = __METHOD__ . ' (' . get_class( $this ) . ')';
- wfProfileIn( $fname );
+ $section = Profiler::instance()->scopedProfileIn( $fname );
// @todo This should probably compare to DIR_DESCENDING and DIR_ASCENDING constants
$descending = ( $this->mIsBackwards == $this->mDefaultDirection );
@@ -226,8 +226,6 @@ abstract class IndexPager extends ContextSource implements Pager {
$this->preprocessResults( $this->mResult );
$this->mResult->rewind(); // Paranoia
-
- wfProfileOut( $fname );
}
/**
@@ -478,7 +476,6 @@ abstract class IndexPager extends ContextSource implements Pager {
$attrs['class'] = "mw-{$type}link";
}
-
return Linker::linkKnown(
$this->getTitle(),
$text,
diff --git a/includes/parser/CacheTime.php b/includes/parser/CacheTime.php
index 94abc266..950c0d46 100644
--- a/includes/parser/CacheTime.php
+++ b/includes/parser/CacheTime.php
@@ -34,8 +34,7 @@ class CacheTime {
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}}
+ $mCacheExpiry = null, # Seconds after which the object should expire, use 0 for uncacheable. Used in ParserCache.
$mCacheRevisionId = null; # Revision ID that was parsed
/**
@@ -46,21 +45,6 @@ class CacheTime {
}
/**
- * @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 does not control expiry, see updateCacheExpiry() for that!
* @param string $t
@@ -123,7 +107,7 @@ class CacheTime {
if ( $this->mCacheTime < 0 ) {
return 0;
- } // old-style marker for "not cachable"
+ } // old-style marker for "not cacheable"
$expire = $this->mCacheExpiry;
@@ -133,12 +117,8 @@ class CacheTime {
$expire = min( $expire, $wgParserCacheExpireTime );
}
- if ( $this->containsOldMagic() ) { //compatibility hack
- $expire = min( $expire, 3600 ); # 1 hour
- }
-
if ( $expire <= 0 ) {
- return 0; // not cachable
+ return 0; // not cacheable
} else {
return $expire;
}
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index eacbecd4..830a68fc 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -36,7 +36,7 @@ class CoreParserFunctions {
# 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:...}}
+ # optional Parser::SFH_NO_HASH to omit the hash from calls (e.g. {{int:...}}
# instead of {{#int:...}})
$noHashFunctions = array(
'ns', 'nse', 'urlencode', 'lcfirst', 'ucfirst', 'lc', 'uc',
@@ -44,7 +44,7 @@ class CoreParserFunctions {
'canonicalurle', 'formatnum', 'grammar', 'gender', 'plural',
'numberofpages', 'numberofusers', 'numberofactiveusers',
'numberofarticles', 'numberoffiles', 'numberofadmins',
- 'numberingroup', 'numberofedits', 'numberofviews', 'language',
+ 'numberingroup', 'numberofedits', 'language',
'padleft', 'padright', 'anchorencode', 'defaultsort', 'filepath',
'pagesincategory', 'pagesize', 'protectionlevel',
'namespacee', 'namespacenumber', 'talkspace', 'talkspacee',
@@ -57,24 +57,24 @@ class CoreParserFunctions {
'revisiontimestamp', 'revisionuser', 'cascadingsources',
);
foreach ( $noHashFunctions as $func ) {
- $parser->setFunctionHook( $func, array( __CLASS__, $func ), SFH_NO_HASH );
+ $parser->setFunctionHook( $func, array( __CLASS__, $func ), Parser::SFH_NO_HASH );
}
- $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH );
- $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), Parser::SFH_NO_HASH );
+ $parser->setFunctionHook( 'int', array( __CLASS__, 'intFunction' ), Parser::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( 'tag', array( __CLASS__, 'tagObj' ), Parser::SFH_OBJECT_ARGS );
$parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) );
if ( $wgAllowDisplayTitle ) {
- $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), Parser::SFH_NO_HASH );
}
if ( $wgAllowSlowParserFunctions ) {
$parser->setFunctionHook(
'pagesinnamespace',
array( __CLASS__, 'pagesinnamespace' ),
- SFH_NO_HASH
+ Parser::SFH_NO_HASH
);
}
}
@@ -111,7 +111,7 @@ class CoreParserFunctions {
$pref = $parser->getOptions()->getDateFormat();
- // Specify a different default date format other than the the normal default
+ // Specify a different default date format other than the normal default
// if the user has 'default' for their setting
if ( $pref == 'default' && $defaultPref ) {
$pref = $defaultPref;
@@ -309,15 +309,12 @@ class CoreParserFunctions {
* @return string
*/
public static function gender( $parser, $username ) {
- wfProfileIn( __METHOD__ );
$forms = array_slice( func_get_args(), 2 );
// Some shortcuts to avoid loading user data unnecessarily
if ( count( $forms ) === 0 ) {
- wfProfileOut( __METHOD__ );
return '';
} elseif ( count( $forms ) === 1 ) {
- wfProfileOut( __METHOD__ );
return $forms[0];
}
@@ -341,7 +338,6 @@ class CoreParserFunctions {
$gender = GenderCache::singleton()->getGenderOf( $parser->getOptions()->getUser(), __METHOD__ );
}
$ret = $parser->getFunctionLang()->gender( $gender, $forms );
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -379,8 +375,7 @@ class CoreParserFunctions {
$text = $parser->doQuotes( $text );
// remove stripped text (e.g. the UNIQ-QINU stuff) that was generated by tag extensions/whatever
- $text = preg_replace( '/' . preg_quote( $parser->uniqPrefix(), '/' ) . '.*?'
- . preg_quote( Parser::MARKER_SUFFIX, '/' ) . '/', '', $text );
+ $text = $parser->killMarkers( $text );
// list of disallowed tags for DISPLAYTITLE
// these will be escaped even though they are allowed in normal wiki text
@@ -489,10 +484,6 @@ class CoreParserFunctions {
public static function numberofedits( $parser, $raw = null ) {
return self::formatRaw( SiteStats::edits(), $raw );
}
- public static function numberofviews( $parser, $raw = null ) {
- global $wgDisableCounters;
- return !$wgDisableCounters ? self::formatRaw( SiteStats::views(), $raw ) : '';
- }
public static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) {
return self::formatRaw( SiteStats::pagesInNs( intval( $namespace ) ), $raw );
}
@@ -902,9 +893,17 @@ 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}}.
+ *
+ * @param Parser $parser
+ * @param string $name
+ * @param string $argA
+ * @param string $argB
+ * @return array|string
+ */
public static function filepath( $parser, $name = '', $argA = '', $argB = '' ) {
$file = wfFindFile( $name );
@@ -943,7 +942,7 @@ class CoreParserFunctions {
* Parser function to extension tag adaptor
* @param Parser $parser
* @param PPFrame $frame
- * @param array $args
+ * @param PPNode[] $args
* @return string
*/
public static function tagObj( $parser, $frame, $args ) {
@@ -958,13 +957,6 @@ class CoreParserFunctions {
$inner = null;
}
- $stripList = $parser->getStripList();
- if ( !in_array( $tagName, $stripList ) ) {
- return '<span class="error">' .
- wfMessage( 'unknown_extension_tag', $tagName )->inContentLanguage()->text() .
- '</span>';
- }
-
$attributes = array();
foreach ( $args as $arg ) {
$bits = $arg->splitArg();
@@ -978,6 +970,19 @@ class CoreParserFunctions {
}
}
+ $stripList = $parser->getStripList();
+ if ( !in_array( $tagName, $stripList ) ) {
+ // we can't handle this tag (at least not now), so just re-emit it as an ordinary tag
+ $attrText = '';
+ foreach ( $attributes as $name => $value ) {
+ $attrText .= ' ' . htmlspecialchars( $name ) . '="' . htmlspecialchars( $value ) . '"';
+ }
+ if ( $inner === null ) {
+ return "<$tagName$attrText/>";
+ }
+ return "<$tagName$attrText>$inner</$tagName>";
+ }
+
$params = array(
'name' => $tagName,
'inner' => $inner,
@@ -1000,11 +1005,6 @@ class CoreParserFunctions {
* @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;
}
@@ -1024,22 +1024,18 @@ class CoreParserFunctions {
// Normalize name for cache
$page = $title->getPrefixedDBkey();
- if ( $cache->has( $page ) ) { // cache contains null values
- return $cache->get( $page );
+ if ( !( $parser->currentRevisionCache && $parser->currentRevisionCache->has( $page ) )
+ && !$parser->incrementExpensiveFunctionCount() ) {
+ return null;
}
- 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
+ $rev = $parser->fetchCurrentRevisionOfTitle( $title );
+ $pageID = $rev ? $rev->getPage() : 0;
+ $revID = $rev ? $rev->getId() : 0;
- // Register dependency in templatelinks
- $parser->getOutput()->addTemplate( $title, $pageID, $revID );
+ // Register dependency in templatelinks
+ $parser->getOutput()->addTemplate( $title, $pageID, $revID );
- return $rev;
- }
- $cache->set( $page, null );
- return null;
+ return $rev;
}
/**
diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php
index 85920cc1..9755ea93 100644
--- a/includes/parser/CoreTagHooks.php
+++ b/includes/parser/CoreTagHooks.php
@@ -35,6 +35,7 @@ class CoreTagHooks {
$parser->setHook( 'pre', array( __CLASS__, 'pre' ) );
$parser->setHook( 'nowiki', array( __CLASS__, 'nowiki' ) );
$parser->setHook( 'gallery', array( __CLASS__, 'gallery' ) );
+ $parser->setHook( 'indicator', array( __CLASS__, 'indicator' ) );
if ( $wgRawHtml ) {
$parser->setHook( 'html', array( __CLASS__, 'html' ) );
}
@@ -119,4 +120,30 @@ class CoreTagHooks {
public static function gallery( $content, $attributes, $parser ) {
return $parser->renderImageGallery( $content, $attributes );
}
+
+ /**
+ * XML-style tag for page status indicators: icons (or short text snippets) usually displayed in
+ * the top-right corner of the page, outside of the main content.
+ *
+ * @param string $content
+ * @param array $attributes
+ * @param Parser $parser
+ * @param PPFrame $frame
+ * @return string
+ * @since 1.25
+ */
+ public static function indicator( $content, array $attributes, Parser $parser, PPFrame $frame ) {
+ if ( !isset( $attributes['name'] ) || trim( $attributes['name'] ) === '' ) {
+ return '<span class="error">' .
+ wfMessage( 'invalid-indicator-name' )->inContentLanguage()->parse() .
+ '</span>';
+ }
+
+ $parser->getOutput()->setIndicator(
+ trim( $attributes['name'] ),
+ Parser::stripOuterParagraph( $parser->recursiveTagParseFully( $content, $frame ) )
+ );
+
+ return '';
+ }
}
diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php
index 82f0e9d4..ef295ab2 100644
--- a/includes/parser/DateFormatter.php
+++ b/includes/parser/DateFormatter.php
@@ -33,7 +33,7 @@ class DateFormatter {
public $regexes, $pDays, $pMonths, $pYears;
public $rules, $xMonths, $preferences;
- protected $lang;
+ protected $lang, $mLinked;
const ALL = -1;
const NONE = 0;
@@ -315,8 +315,8 @@ class DateFormatter {
}
/**
- * @todo document
- * @return string
+ * Return a regex that can be used to find month names in string
+ * @return string regex to find the months with
*/
public function getMonthRegex() {
$names = array();
@@ -338,7 +338,7 @@ class DateFormatter {
}
/**
- * @todo document
+ * Make an ISO year from a year name, for instance: '-1199' from '1200 BC'
* @param string $year Year name
* @return string ISO year name
*/
@@ -356,9 +356,10 @@ class DateFormatter {
}
/**
- * @todo document
- * @param string $iso
- * @return int|string
+ * Make a year one from an ISO year, for instance: '400 BC' from '-0399'.
+ * @param string $iso ISO year
+ * @return int|string int representing year number in case of AD dates, or string containing
+ * year number and 'BC' at the end otherwise.
*/
public function makeNormalYear( $iso ) {
if ( $iso[0] == '-' ) {
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index 7794fae4..7026c5ce 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -229,7 +229,6 @@ class LinkHolderArray {
* @return string
*/
public function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
- wfProfileIn( __METHOD__ );
if ( !is_object( $nt ) ) {
# Fail gracefully
$retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
@@ -259,7 +258,6 @@ class LinkHolderArray {
}
$this->size++;
}
- wfProfileOut( __METHOD__ );
return $retVal;
}
@@ -267,17 +265,12 @@ class LinkHolderArray {
* Replace <!--LINK--> link placeholders with actual links, in the buffer
*
* @param string $text
- * @return array Array of link CSS classes, indexed by PDBK.
*/
public function replace( &$text ) {
- wfProfileIn( __METHOD__ );
- /** @todo FIXME: replaceInternal doesn't return a value */
- $colours = $this->replaceInternal( $text );
+ $this->replaceInternal( $text );
$this->replaceInterwiki( $text );
- wfProfileOut( __METHOD__ );
- return $colours;
}
/**
@@ -289,14 +282,12 @@ class LinkHolderArray {
return;
}
- wfProfileIn( __METHOD__ );
global $wgContLang, $wgContentHandlerUseDB;
$colours = array();
$linkCache = LinkCache::singleton();
$output = $this->parent->getOutput();
- wfProfileIn( __METHOD__ . '-check' );
$dbr = wfGetDB( DB_SLAVE );
$threshold = $this->parent->getOptions()->getStubThreshold();
@@ -380,9 +371,8 @@ class LinkHolderArray {
}
if ( count( $linkcolour_ids ) ) {
//pass an array of page_ids to an extension
- wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+ Hooks::run( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
}
- wfProfileOut( __METHOD__ . '-check' );
# Do a second query for different language variants of links and categories
if ( $wgContLang->hasVariants() ) {
@@ -390,7 +380,6 @@ class LinkHolderArray {
}
# Construct search and replace arrays
- wfProfileIn( __METHOD__ . '-construct' );
$replacePairs = array();
foreach ( $this->internals as $ns => $entries ) {
foreach ( $entries as $index => $entry ) {
@@ -426,18 +415,14 @@ class LinkHolderArray {
}
}
$replacer = new HashtableReplacer( $replacePairs, 1 );
- wfProfileOut( __METHOD__ . '-construct' );
# Do the thing
- wfProfileIn( __METHOD__ . '-replace' );
$text = preg_replace_callback(
'/(<!--LINK .*?-->)/',
$replacer->cb(),
$text
);
- wfProfileOut( __METHOD__ . '-replace' );
- wfProfileOut( __METHOD__ );
}
/**
@@ -449,7 +434,6 @@ class LinkHolderArray {
return;
}
- wfProfileIn( __METHOD__ );
# Make interwiki link HTML
$output = $this->parent->getOutput();
$replacePairs = array();
@@ -463,7 +447,6 @@ class LinkHolderArray {
'/<!--IWLINK (.*?)-->/',
$replacer->cb(),
$text );
- wfProfileOut( __METHOD__ );
}
/**
@@ -617,7 +600,7 @@ class LinkHolderArray {
}
}
}
- wfRunHooks( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
+ Hooks::run( 'GetLinkColours', array( $linkcolour_ids, &$colours ) );
// rebuild the categories in original order (if there are replacements)
if ( count( $varCategories ) > 0 ) {
@@ -644,14 +627,12 @@ class LinkHolderArray {
* @return string
*/
public function replaceText( $text ) {
- wfProfileIn( __METHOD__ );
$text = preg_replace_callback(
'/<!--(LINK|IWLINK) (.*?)-->/',
array( &$this, 'replaceTextCallback' ),
$text );
- wfProfileOut( __METHOD__ );
return $text;
}
diff --git a/includes/parser/MWTidy.php b/includes/parser/MWTidy.php
index b310862f..d446ccf6 100644
--- a/includes/parser/MWTidy.php
+++ b/includes/parser/MWTidy.php
@@ -127,17 +127,11 @@ class MWTidy {
* @return string Corrected HTML output
*/
public static function tidy( $text ) {
- global $wgTidyInternal;
-
$wrapper = new MWTidyWrapper;
$wrappedtext = $wrapper->getWrapped( $text );
$retVal = null;
- if ( $wgTidyInternal ) {
- $correctedtext = self::execInternalTidy( $wrappedtext, false, $retVal );
- } else {
- $correctedtext = self::execExternalTidy( $wrappedtext, false, $retVal );
- }
+ $correctedtext = self::clean( $wrappedtext, false, $retVal );
if ( $retVal < 0 ) {
wfDebug( "Possible tidy configuration error!\n" );
@@ -160,16 +154,34 @@ class MWTidy {
* @return bool Whether the HTML is valid
*/
public static function checkErrors( $text, &$errorStr = null ) {
+ $retval = 0;
+ $errorStr = self::clean( $text, true, $retval );
+ return ( $retval < 0 && $errorStr == '' ) || $retval == 0;
+ }
+
+ /**
+ * Perform a clean/repair operation
+ * @param string $text HTML to check
+ * @param bool $stderr Whether to read result from STDERR rather than STDOUT
+ * @param int &$retval Exit code (-1 on internal error)
+ * @return null|string
+ * @throws MWException
+ */
+ private static function clean( $text, $stderr = false, &$retval = null ) {
global $wgTidyInternal;
- $retval = 0;
if ( $wgTidyInternal ) {
- $errorStr = self::execInternalTidy( $text, true, $retval );
+ if ( wfIsHHVM() ) {
+ if ( $stderr ) {
+ throw new MWException( __METHOD__ . ": error text return from HHVM tidy is not supported" );
+ }
+ return self::hhvmClean( $text, $retval );
+ } else {
+ return self::phpClean( $text, $stderr, $retval );
+ }
} else {
- $errorStr = self::execExternalTidy( $text, true, $retval );
+ return self::externalClean( $text, $stderr, $retval );
}
-
- return ( $retval < 0 && $errorStr == '' ) || $retval == 0;
}
/**
@@ -181,9 +193,8 @@ class MWTidy {
* @param int &$retval Exit code (-1 on internal error)
* @return string|null
*/
- private static function execExternalTidy( $text, $stderr = false, &$retval = null ) {
+ private static function externalClean( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
- wfProfileIn( __METHOD__ );
$cleansource = '';
$opts = ' -utf8';
@@ -235,7 +246,6 @@ class MWTidy {
$cleansource = null;
}
- wfProfileOut( __METHOD__ );
return $cleansource;
}
@@ -248,15 +258,15 @@ class MWTidy {
* @param int &$retval Exit code (-1 on internal error)
* @return string|null
*/
- private static function execInternalTidy( $text, $stderr = false, &$retval = null ) {
+ private static function phpClean( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgDebugTidy;
- wfProfileIn( __METHOD__ );
- if ( !class_exists( 'tidy' ) ) {
+ if ( ( !wfIsHHVM() && !class_exists( 'tidy' ) ) ||
+ ( wfIsHHVM() && !function_exists( 'tidy_repair_string' ) )
+ ) {
wfWarn( "Unable to load internal tidy class." );
$retval = -1;
- wfProfileOut( __METHOD__ );
return null;
}
@@ -265,8 +275,6 @@ class MWTidy {
if ( $stderr ) {
$retval = $tidy->getStatus();
-
- wfProfileOut( __METHOD__ );
return $tidy->errorBuffer;
}
@@ -285,7 +293,31 @@ class MWTidy {
}
}
- wfProfileOut( __METHOD__ );
+ return $cleansource;
+ }
+
+ /**
+ * Use the tidy extension for HHVM from
+ * https://github.com/wikimedia/mediawiki-php-tidy
+ *
+ * This currently does not support the object-oriented interface, but
+ * tidy_repair_string() can be used for the most common tasks.
+ *
+ * @param string $text HTML to check
+ * @param int &$retval Exit code (-1 on internal error)
+ * @return string|null
+ */
+ private static function hhvmClean( $text, &$retval ) {
+ global $wgTidyConf;
+
+ $cleansource = tidy_repair_string( $text, $wgTidyConf, 'utf8' );
+ if ( $cleansource === false ) {
+ $cleansource = null;
+ $retval = -1;
+ } else {
+ $retval = 0;
+ }
+
return $cleansource;
}
}
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 84bb2243..ace63a09 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -35,18 +35,18 @@
*
* - Parser::parse()
* produces HTML output
- * - Parser::preSaveTransform().
- * produces altered wiki markup.
+ * - Parser::preSaveTransform()
+ * produces altered wiki markup
* - Parser::preprocess()
* removes HTML comments and expands templates
* - Parser::cleanSig() and Parser::cleanSigInSig()
- * Cleans a signature before saving it to preferences
+ * cleans a signature before saving it to preferences
* - Parser::getSection()
- * Return the content of a section from an article for section editing
+ * return the content of a section from an article for section editing
* - Parser::replaceSection()
- * Replaces a section by number inside an article
+ * replaces a section by number inside an article
* - Parser::getPreloadText()
- * Removes <noinclude> sections, and <includeonly> tags.
+ * removes <noinclude> sections and <includeonly> tags
*
* Globals used:
* object: $wgContLang
@@ -79,7 +79,6 @@ class Parser {
const HALF_PARSED_VERSION = 2;
# Flags for Parser::setFunctionHook
- # Also available as global constants from Defines.php
const SFH_NO_HASH = 1;
const SFH_OBJECT_ARGS = 2;
@@ -91,6 +90,9 @@ class Parser {
const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F\p{Zs}]+)
\\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
+ # Regular expression for a non-newline space
+ const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
+
# State constants for the definition list colon extraction
const COLON_STATE_TEXT = 0;
const COLON_STATE_TAG = 1;
@@ -144,7 +146,12 @@ class Parser {
* @var MagicWordArray
*/
public $mSubstWords;
- public $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
+ # Initialised in constructor
+ public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
+
+ # Initialized in getPreprocessor()
+ /** @var Preprocessor */
+ public $mPreprocessor;
# Cleared with clearState():
/**
@@ -211,11 +218,22 @@ class Parser {
public $mLangLinkLanguages;
/**
+ * @var MapCacheLRU|null
+ * @since 1.24
+ *
+ * A cache of the current revisions of titles. Keys are $title->getPrefixedDbKey()
+ */
+ public $currentRevisionCache;
+
+ /**
* @var bool Recursive call protection.
* This variable should be treated as if it were private.
*/
public $mInParse = false;
+ /** @var SectionProfiler */
+ protected $mProfiler;
+
/**
* @param array $conf
*/
@@ -258,7 +276,22 @@ class Parser {
*/
public function __clone() {
$this->mInParse = false;
- wfRunHooks( 'ParserCloned', array( $this ) );
+
+ // Bug 56226: When you create a reference "to" an object field, that
+ // makes the object field itself be a reference too (until the other
+ // reference goes out of scope). When cloning, any field that's a
+ // reference is copied as a reference in the new object. Both of these
+ // are defined PHP5 behaviors, as inconvenient as it is for us when old
+ // hooks from PHP4 days are passing fields by reference.
+ foreach ( array( 'mStripState', 'mVarCache' ) as $k ) {
+ // Make a non-reference copy of the field, then rebind the field to
+ // reference the new copy.
+ $tmp = $this->$k;
+ $this->$k =& $tmp;
+ unset( $tmp );
+ }
+
+ Hooks::run( 'ParserCloned', array( $this ) );
}
/**
@@ -270,14 +303,11 @@ class Parser {
}
$this->mFirstCall = false;
- wfProfileIn( __METHOD__ );
-
CoreParserFunctions::register( $this );
CoreTagHooks::register( $this );
$this->initialiseVariables();
- wfRunHooks( 'ParserFirstCallInit', array( &$this ) );
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'ParserFirstCallInit', array( &$this ) );
}
/**
@@ -286,7 +316,6 @@ class Parser {
* @private
*/
public function clearState() {
- wfProfileIn( __METHOD__ );
if ( $this->mFirstCall ) {
$this->firstCallInit();
}
@@ -305,6 +334,7 @@ class Parser {
$this->mVarCache = array();
$this->mUser = null;
$this->mLangLinkLanguages = array();
+ $this->currentRevisionCache = null;
/**
* Prefix for temporary replacement strings for the multipass parser.
@@ -341,8 +371,9 @@ class Parser {
$this->mPreprocessor = null;
}
- wfRunHooks( 'ParserClearState', array( &$this ) );
- wfProfileOut( __METHOD__ );
+ $this->mProfiler = new SectionProfiler();
+
+ Hooks::run( 'ParserClearState', array( &$this ) );
}
/**
@@ -365,10 +396,7 @@ class Parser {
* to internalParse() which does all the real work.
*/
- global $wgUseTidy, $wgAlwaysUseTidy, $wgShowHostnames;
- $fname = __METHOD__ . '-' . wfGetCaller();
- wfProfileIn( __METHOD__ );
- wfProfileIn( $fname );
+ global $wgShowHostnames;
if ( $clearState ) {
$magicScopeVariable = $this->lock();
@@ -376,6 +404,7 @@ class Parser {
$this->startParse( $title, $options, self::OT_HTML, $clearState );
+ $this->currentRevisionCache = null;
$this->mInputSize = strlen( $text );
if ( $this->mOptions->getEnableLimitReport() ) {
$this->mOutput->resetParseStartTime();
@@ -399,46 +428,13 @@ class Parser {
$this->mRevisionSize = null;
}
- wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
# No more strip!
- wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->internalParse( $text );
- wfRunHooks( 'ParserAfterParse', array( &$this, &$text, &$this->mStripState ) );
-
- $text = $this->mStripState->unstripGeneral( $text );
+ Hooks::run( 'ParserAfterParse', array( &$this, &$text, &$this->mStripState ) );
- # Clean up special characters, only run once, next-to-last before doBlockLevels
- $fixtags = array(
- # french spaces, last one Guillemet-left
- # only if there is something before the space
- '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
- # french spaces, Guillemet-right
- '/(\\302\\253) /' => '\\1&#160;',
- '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
- );
- $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
-
- $text = $this->doBlockLevels( $text, $linestart );
-
- $this->replaceLinkHolders( $text );
-
- /**
- * The input doesn't get language converted if
- * a) It's disabled
- * b) Content isn't converted
- * c) It's a conversion table
- * d) it is an interface message (which is in the user language)
- */
- if ( !( $options->getDisableContentConversion()
- || 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
- # is the <nowiki> mark.
- $text = $this->getConverterLanguage()->convert( $text );
- }
- }
+ $text = $this->internalParseHalfParsed( $text, true, $linestart );
/**
* A converted title will be provided in the output object if title and
@@ -461,45 +457,6 @@ class Parser {
}
}
- $text = $this->mStripState->unstripNoWiki( $text );
-
- wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) );
-
- $text = $this->replaceTransparentTags( $text );
- $text = $this->mStripState->unstripGeneral( $text );
-
- $text = Sanitizer::normalizeCharReferences( $text );
-
- if ( ( $wgUseTidy && $this->mOptions->getTidy() ) || $wgAlwaysUseTidy ) {
- $text = MWTidy::tidy( $text );
- } else {
- # attempt to sanitize at least some nesting problems
- # (bug #2702 and quite a few others)
- $tidyregs = array(
- # ''Something [http://www.cool.com cool''] -->
- # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
- '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
- '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
- # fix up an anchor inside another anchor, only
- # at least for a single single nested link (bug 3695)
- '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
- '\\1\\2</a>\\3</a>\\1\\4</a>',
- # fix div inside inline elements- doBlockLevels won't wrap a line which
- # contains a div, so fix it up here; replace
- # div with escaped text
- '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
- '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
- # remove empty italic or bold tag pairs, some
- # introduced by rules above
- '/<([bi])><\/\\1>/' => '',
- );
-
- $text = preg_replace(
- array_keys( $tidyregs ),
- array_values( $tidyregs ),
- $text );
- }
-
if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
$this->limitationWarn( 'expensive-parserfunction',
$this->mExpensiveFunctionCount,
@@ -507,8 +464,6 @@ class Parser {
);
}
- wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
-
# Information on include size limits, for the benefit of users who try to skirt them
if ( $this->mOptions->getEnableLimitReport() ) {
$max = $this->mOptions->getMaxIncludeSize();
@@ -543,14 +498,14 @@ class Parser {
$this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
array( $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() )
);
- wfRunHooks( 'ParserLimitReportPrepare', array( $this, $this->mOutput ) );
+ Hooks::run( 'ParserLimitReportPrepare', array( $this, $this->mOutput ) );
$limitReport = "NewPP limit report\n";
if ( $wgShowHostnames ) {
$limitReport .= 'Parsed by ' . wfHostname() . "\n";
}
foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
- if ( wfRunHooks( 'ParserLimitReportFormat',
+ if ( Hooks::run( 'ParserLimitReportFormat',
array( $key, &$value, &$limitReport, false, false )
) ) {
$keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
@@ -568,13 +523,26 @@ class Parser {
// Since we're not really outputting HTML, decode the entities and
// then re-encode the things that need hiding inside HTML comments.
$limitReport = htmlspecialchars_decode( $limitReport );
- wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
+ Hooks::run( 'ParserLimitReport', array( $this, &$limitReport ) );
// Sanitize for comment. Note '‐' in the replacement is U+2010,
// which looks much like the problematic '-'.
$limitReport = str_replace( array( '-', '&' ), array( '‐', '&amp;' ), $limitReport );
$text .= "\n<!-- \n$limitReport-->\n";
+ // Add on template profiling data
+ $dataByFunc = $this->mProfiler->getFunctionStats();
+ uasort( $dataByFunc, function ( $a, $b ) {
+ return $a['real'] < $b['real']; // descending order
+ } );
+ $profileReport = "Transclusion expansion time report (%,ms,calls,template)\n";
+ foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
+ $profileReport .= sprintf( "%6.2f%% %8.3f %6d - %s\n",
+ $item['%real'], $item['real'], $item['calls'],
+ htmlspecialchars( $item['name'] ) );
+ }
+ $text .= "\n<!-- \n$profileReport-->\n";
+
if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
$this->mTitle->getPrefixedDBkey() );
@@ -588,29 +556,60 @@ class Parser {
$this->mRevisionUser = $oldRevisionUser;
$this->mRevisionSize = $oldRevisionSize;
$this->mInputSize = false;
- wfProfileOut( $fname );
- wfProfileOut( __METHOD__ );
+ $this->currentRevisionCache = null;
return $this->mOutput;
}
/**
- * Recursive parser entry point that can be called from an extension tag
- * hook.
+ * Half-parse wikitext to half-parsed HTML. This recursive parser entry point
+ * can be called from an extension tag hook.
+ *
+ * The output of this function IS NOT SAFE PARSED HTML; it is "half-parsed"
+ * instead, which means that lists and links have not been fully parsed yet,
+ * and strip markers are still present.
*
- * If $frame is not provided, then template variables (e.g., {{{1}}}) within $text are not expanded
+ * Use recursiveTagParseFully() to fully parse wikitext to output-safe HTML.
+ *
+ * Use this function if you're a parser tag hook and you want to parse
+ * wikitext before or after applying additional transformations, and you
+ * intend to *return the result as hook output*, which will cause it to go
+ * through the rest of parsing process automatically.
+ *
+ * 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 bool|PPFrame $frame The frame to use for expanding any template variables
- *
- * @return string
+ * @return string UNSAFE half-parsed HTML
*/
public function recursiveTagParse( $text, $frame = false ) {
- wfProfileIn( __METHOD__ );
- wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
- wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->internalParse( $text, false, $frame );
- wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
+ * Fully parse wikitext to fully parsed HTML. This recursive parser entry
+ * point can be called from an extension tag hook.
+ *
+ * The output of this function is fully-parsed HTML that is safe for output.
+ * If you're a parser tag hook, you might want to use recursiveTagParse()
+ * instead.
+ *
+ * If $frame is not provided, then template variables (e.g., {{{1}}}) within
+ * $text are not expanded
+ *
+ * @since 1.25
+ *
+ * @param string $text Text extension wants to have parsed
+ * @param bool|PPFrame $frame The frame to use for expanding any template variables
+ * @return string Fully parsed HTML
+ */
+ public function recursiveTagParseFully( $text, $frame = false ) {
+ $text = $this->recursiveTagParse( $text, $frame );
+ $text = $this->internalParseHalfParsed( $text, false );
return $text;
}
@@ -625,18 +624,18 @@ class Parser {
* @param bool|PPFrame $frame
* @return mixed|string
*/
- public function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null, $frame = false ) {
- wfProfileIn( __METHOD__ );
+ public function preprocess( $text, Title $title = null,
+ ParserOptions $options, $revid = null, $frame = false
+ ) {
$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 ) );
+ Hooks::run( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
$text = $this->replaceVariables( $text, $frame );
$text = $this->mStripState->unstripBoth( $text );
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -650,10 +649,8 @@ class Parser {
* @since 1.19
*/
public function recursivePreprocess( $text, $frame = false ) {
- wfProfileIn( __METHOD__ );
$text = $this->replaceVariables( $text, $frame );
$text = $this->mStripState->unstripBoth( $text );
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -1008,7 +1005,6 @@ class Parser {
* @return string
*/
public function doTableStuff( $text ) {
- wfProfileIn( __METHOD__ );
$lines = StringUtils::explode( "\n", $text );
$out = '';
@@ -1195,31 +1191,27 @@ class Parser {
$out = '';
}
- wfProfileOut( __METHOD__ );
-
return $out;
}
/**
- * Helper function for parse() that transforms wiki markup into
+ * Helper function for parse() that transforms wiki markup into half-parsed
* HTML. Only called for $mOutputType == self::OT_HTML.
*
* @private
*
* @param string $text
* @param bool $isMain
- * @param bool $frame
+ * @param PPFrame|bool $frame
*
* @return string
*/
public function internalParse( $text, $isMain = true, $frame = false ) {
- wfProfileIn( __METHOD__ );
$origText = $text;
# Hook to suspend the parser in this state
- if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
- wfProfileOut( __METHOD__ );
+ if ( !Hooks::run( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
return $text;
}
@@ -1239,14 +1231,14 @@ class Parser {
$text = $this->replaceVariables( $text );
}
- wfRunHooks( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) );
$text = Sanitizer::removeHTMLtags(
$text,
array( &$this, 'attributeStripCallback' ),
false,
array_keys( $this->mTransparentTagHooks )
);
- wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
+ Hooks::run( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
# Tables need to come after variable replacement for things to work
# properly; putting them before other transformations should keep
@@ -1270,7 +1262,101 @@ class Parser {
$text = $this->doMagicLinks( $text );
$text = $this->formatHeadings( $text, $origText, $isMain );
- wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
+ * Helper function for parse() that transforms half-parsed HTML into fully
+ * parsed HTML.
+ *
+ * @param string $text
+ * @param bool $isMain
+ * @param bool $linestart
+ * @return string
+ */
+ private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
+ global $wgUseTidy, $wgAlwaysUseTidy;
+
+ $text = $this->mStripState->unstripGeneral( $text );
+
+ # Clean up special characters, only run once, next-to-last before doBlockLevels
+ $fixtags = array(
+ # french spaces, last one Guillemet-left
+ # only if there is something before the space
+ '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
+ # french spaces, Guillemet-right
+ '/(\\302\\253) /' => '\\1&#160;',
+ '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
+ );
+ $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
+
+ $text = $this->doBlockLevels( $text, $linestart );
+
+ $this->replaceLinkHolders( $text );
+
+ /**
+ * The input doesn't get language converted if
+ * a) It's disabled
+ * b) Content isn't converted
+ * c) It's a conversion table
+ * d) it is an interface message (which is in the user language)
+ */
+ if ( !( $this->mOptions->getDisableContentConversion()
+ || 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
+ # is the <nowiki> mark.
+ $text = $this->getConverterLanguage()->convert( $text );
+ }
+ }
+
+ $text = $this->mStripState->unstripNoWiki( $text );
+
+ if ( $isMain ) {
+ Hooks::run( 'ParserBeforeTidy', array( &$this, &$text ) );
+ }
+
+ $text = $this->replaceTransparentTags( $text );
+ $text = $this->mStripState->unstripGeneral( $text );
+
+ $text = Sanitizer::normalizeCharReferences( $text );
+
+ if ( ( $wgUseTidy && $this->mOptions->getTidy() ) || $wgAlwaysUseTidy ) {
+ $text = MWTidy::tidy( $text );
+ } else {
+ # attempt to sanitize at least some nesting problems
+ # (bug #2702 and quite a few others)
+ $tidyregs = array(
+ # ''Something [http://www.cool.com cool''] -->
+ # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
+ '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
+ '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
+ # fix up an anchor inside another anchor, only
+ # at least for a single single nested link (bug 3695)
+ '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
+ '\\1\\2</a>\\3</a>\\1\\4</a>',
+ # fix div inside inline elements- doBlockLevels won't wrap a line which
+ # contains a div, so fix it up here; replace
+ # div with escaped text
+ '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
+ '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
+ # remove empty italic or bold tag pairs, some
+ # introduced by rules above
+ '/<([bi])><\/\\1>/' => '',
+ );
+
+ $text = preg_replace(
+ array_keys( $tidyregs ),
+ array_values( $tidyregs ),
+ $text );
+ }
+
+ if ( $isMain ) {
+ Hooks::run( 'ParserAfterTidy', array( &$this, &$text ) );
+ }
+
return $text;
}
@@ -1286,22 +1372,24 @@ class Parser {
* @return string
*/
public function doMagicLinks( $text ) {
- wfProfileIn( __METHOD__ );
$prots = wfUrlProtocolsWithoutProtRel();
$urlChar = self::EXT_LINK_URL_CLASS;
+ $space = self::SPACE_NOT_NL; # non-newline space
+ $spdash = "(?:-|$space)"; # a dash or a non-newline space
+ $spaces = "$space++"; # possessive match of 1 or more spaces
$text = preg_replace_callback(
'!(?: # Start cases
(<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
(<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
- (\\b(?i:$prots)$urlChar+) | # m[3]: Free external links" . '
- (?:RFC|PMID)\s+([0-9]+) | # m[4]: RFC or PMID, capture number
- ISBN\s+(\b # m[5]: ISBN, capture number
- (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix
- (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters
+ (\b(?i:$prots)$urlChar+) | # m[3]: Free external links
+ \b(?:RFC|PMID) $spaces # m[4]: RFC or PMID, capture number
+ ([0-9]+)\b |
+ \bISBN $spaces ( # m[5]: ISBN, capture number
+ (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
+ (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
[0-9Xx] # check digit
- \b)
- )!xu', array( &$this, 'magicLinkCallback' ), $text );
- wfProfileOut( __METHOD__ );
+ )\b
+ )!xu", array( &$this, 'magicLinkCallback' ), $text );
return $text;
}
@@ -1341,6 +1429,8 @@ class Parser {
} elseif ( isset( $m[5] ) && $m[5] !== '' ) {
# ISBN
$isbn = $m[5];
+ $space = self::SPACE_NOT_NL; # non-newline space
+ $isbn = preg_replace( "/$space/", ' ', $isbn );
$num = strtr( $isbn, array(
'-' => '',
' ' => '',
@@ -1364,7 +1454,6 @@ class Parser {
* @private
*/
public function makeFreeExternalLink( $url ) {
- wfProfileIn( __METHOD__ );
$trail = '';
@@ -1384,7 +1473,20 @@ class Parser {
$sep .= ')';
}
- $numSepChars = strspn( strrev( $url ), $sep );
+ $urlRev = strrev( $url );
+ $numSepChars = strspn( $urlRev, $sep );
+ # Don't break a trailing HTML entity by moving the ; into $trail
+ # This is in hot code, so use substr_compare to avoid having to
+ # create a new string object for the comparison
+ if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0) {
+ # more optimization: instead of running preg_match with a $
+ # anchor, which can be slow, do the match on the reversed
+ # string starting at the desired offset.
+ # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
+ if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
+ $numSepChars--;
+ }
+ }
if ( $numSepChars ) {
$trail = substr( $url, -$numSepChars ) . $trail;
$url = substr( $url, 0, -$numSepChars );
@@ -1405,7 +1507,6 @@ class Parser {
$pasteurized = self::normalizeLinkUrl( $url );
$this->mOutput->addExternalLink( $pasteurized );
}
- wfProfileOut( __METHOD__ );
return $text . $trail;
}
@@ -1419,12 +1520,10 @@ class Parser {
* @return string
*/
public function doHeadings( $text ) {
- wfProfileIn( __METHOD__ );
for ( $i = 6; $i >= 1; --$i ) {
$h = str_repeat( '=', $i );
$text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
}
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -1437,14 +1536,12 @@ class Parser {
* @return string The altered text
*/
public function doAllQuotes( $text ) {
- wfProfileIn( __METHOD__ );
$outtext = '';
$lines = StringUtils::explode( "\n", $text );
foreach ( $lines as $line ) {
$outtext .= $this->doQuotes( $line ) . "\n";
}
$outtext = substr( $outtext, 0, -1 );
- wfProfileOut( __METHOD__ );
return $outtext;
}
@@ -1646,11 +1743,9 @@ class Parser {
* @return string
*/
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" );
}
@@ -1714,7 +1809,6 @@ class Parser {
$this->mOutput->addExternalLink( $pasteurized );
}
- wfProfileOut( __METHOD__ );
return $s;
}
@@ -1912,9 +2006,7 @@ class Parser {
*/
public function replaceInternalLinks2( &$s ) {
global $wgExtraInterlanguageLinkPrefixes;
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-setup' );
static $tc = false, $e1, $e1_img;
# the % is needed to support urlencoded titles as well
if ( !$tc ) {
@@ -1946,8 +2038,6 @@ class Parser {
}
if ( is_null( $this->mTitle ) ) {
- wfProfileOut( __METHOD__ . '-setup' );
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
}
$nottalk = !$this->mTitle->isTalkPage();
@@ -1964,7 +2054,6 @@ class Parser {
}
$useSubpages = $this->areSubpagesAllowed();
- wfProfileOut( __METHOD__ . '-setup' );
// @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
# Loop for each link
@@ -1980,7 +2069,6 @@ class Parser {
}
if ( $useLinkPrefixExtension ) {
- wfProfileIn( __METHOD__ . '-prefixhandling' );
if ( preg_match( $e2, $s, $m ) ) {
$prefix = $m[2];
$s = $m[1];
@@ -1992,12 +2080,10 @@ class Parser {
$prefix = $first_prefix;
$first_prefix = false;
}
- wfProfileOut( __METHOD__ . '-prefixhandling' );
}
$might_be_img = false;
- wfProfileIn( __METHOD__ . "-e1" );
if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
$text = $m[2];
# If we get a ] at the beginning of $m[3] that means we have a link that's something like:
@@ -2031,11 +2117,8 @@ class Parser {
$trail = "";
} else { # Invalid form; output directly
$s .= $prefix . '[[' . $line;
- wfProfileOut( __METHOD__ . "-e1" );
continue;
}
- wfProfileOut( __METHOD__ . "-e1" );
- wfProfileIn( __METHOD__ . "-misc" );
$origLink = $m[1];
@@ -2044,7 +2127,6 @@ class Parser {
# should be external links.
if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
$s .= $prefix . '[[' . $line;
- wfProfileOut( __METHOD__ . "-misc" );
continue;
}
@@ -2061,21 +2143,16 @@ class Parser {
$link = substr( $link, 1 );
}
- wfProfileOut( __METHOD__ . "-misc" );
- wfProfileIn( __METHOD__ . "-title" );
$nt = Title::newFromText( $this->mStripState->unstripNoWiki( $link ) );
if ( $nt === null ) {
$s .= $prefix . '[[' . $line;
- wfProfileOut( __METHOD__ . "-title" );
continue;
}
$ns = $nt->getNamespace();
$iw = $nt->getInterwiki();
- wfProfileOut( __METHOD__ . "-title" );
if ( $might_be_img ) { # if this is actually an invalid link
- wfProfileIn( __METHOD__ . "-might_be_img" );
if ( $ns == NS_FILE && $noforce ) { # but might be an image
$found = false;
while ( true ) {
@@ -2107,16 +2184,13 @@ class Parser {
$holders->merge( $this->replaceInternalLinks2( $text ) );
$s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
- wfProfileOut( __METHOD__ . "-might_be_img" );
continue;
}
} else { # it's not an image, so output it raw
$s .= "{$prefix}[[$link|$text";
# note: no $trail, because without an end, there *is* no trail
- wfProfileOut( __METHOD__ . "-might_be_img" );
continue;
}
- wfProfileOut( __METHOD__ . "-might_be_img" );
}
$wasblank = ( $text == '' );
@@ -2133,7 +2207,6 @@ class Parser {
# Link not escaped by : , create the various objects
if ( $noforce && !$nt->wasLocalInterwiki() ) {
# Interwikis
- wfProfileIn( __METHOD__ . "-interwiki" );
if (
$iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
Language::fetchLanguageName( $iw, null, 'mw' ) ||
@@ -2148,13 +2221,10 @@ class Parser {
$s = rtrim( $s . $prefix );
$s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
- wfProfileOut( __METHOD__ . "-interwiki" );
continue;
}
- wfProfileOut( __METHOD__ . "-interwiki" );
if ( $ns == NS_FILE ) {
- wfProfileIn( __METHOD__ . "-image" );
if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
if ( $wasblank ) {
# if no parameters were passed, $text
@@ -2175,12 +2245,10 @@ class Parser {
} else {
$s .= $prefix . $trail;
}
- wfProfileOut( __METHOD__ . "-image" );
continue;
}
if ( $ns == NS_CATEGORY ) {
- wfProfileIn( __METHOD__ . "-category" );
$s = rtrim( $s . "\n" ); # bug 87
if ( $wasblank ) {
@@ -2198,7 +2266,6 @@ class Parser {
*/
$s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
- wfProfileOut( __METHOD__ . "-category" );
continue;
}
}
@@ -2214,22 +2281,19 @@ class Parser {
# NS_MEDIA is a pseudo-namespace for linking directly to a file
# @todo FIXME: Should do batch file existence checks, see comment below
if ( $ns == NS_MEDIA ) {
- wfProfileIn( __METHOD__ . "-media" );
# Give extensions a chance to select the file revision for us
$options = array();
$descQuery = false;
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ Hooks::run( 'BeforeParserFetchFileAndTitle',
array( $this, $nt, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
# Cloak with NOPARSE to avoid replacement in replaceExternalLinks
$s .= $prefix . $this->armorLinks(
Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
- wfProfileOut( __METHOD__ . "-media" );
continue;
}
- wfProfileIn( __METHOD__ . "-always_known" );
# Some titles, such as valid special pages or files in foreign repos, should
# be shown as bluelinks even though they're not included in the page table
#
@@ -2242,9 +2306,7 @@ class Parser {
# Links will be added to the output link list after checking
$s .= $holders->makeHolder( $nt, $text, array(), $trail, $prefix );
}
- wfProfileOut( __METHOD__ . "-always_known" );
}
- wfProfileOut( __METHOD__ );
return $holders;
}
@@ -2443,7 +2505,6 @@ class Parser {
* @return string The lists rendered as HTML
*/
public function doBlockLevels( $text, $linestart ) {
- wfProfileIn( __METHOD__ );
# Parsing through the text line by line. The main thing
# happening here is handling of block-level elements p, pre,
@@ -2552,7 +2613,6 @@ class Parser {
# If we have no prefixes, go to paragraph mode.
if ( 0 == $prefixLength ) {
- 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(
@@ -2568,11 +2628,11 @@ class Parser {
$t
);
- if ( $openmatch or $closematch ) {
+ if ( $openmatch || $closematch ) {
$paragraphStack = false;
# @todo bug 5718: paragraph closed
$output .= $this->closeParagraph();
- if ( $preOpenMatch and !$preCloseMatch ) {
+ if ( $preOpenMatch && !$preCloseMatch ) {
$this->mInPre = true;
}
$bqOffset = 0;
@@ -2621,7 +2681,6 @@ class Parser {
}
}
}
- wfProfileOut( __METHOD__ . "-paragraph" );
}
# somewhere above we forget to get out of pre block (bug 785)
if ( $preCloseMatch && $this->mInPre ) {
@@ -2646,7 +2705,6 @@ class Parser {
$this->mLastSection = '';
}
- wfProfileOut( __METHOD__ );
return $output;
}
@@ -2661,12 +2719,10 @@ class Parser {
* @return string The position of the ':', or false if none found
*/
public function findColonNoLinks( $str, &$before, &$after ) {
- wfProfileIn( __METHOD__ );
$pos = strpos( $str, ':' );
if ( $pos === false ) {
# Nothing to find!
- wfProfileOut( __METHOD__ );
return false;
}
@@ -2675,7 +2731,6 @@ class Parser {
# Easy; no tag nesting to worry about
$before = substr( $str, 0, $pos );
$after = substr( $str, $pos + 1 );
- wfProfileOut( __METHOD__ );
return $pos;
}
@@ -2699,7 +2754,6 @@ class Parser {
# We found it!
$before = substr( $str, 0, $i );
$after = substr( $str, $i + 1 );
- wfProfileOut( __METHOD__ );
return $i;
}
# Embedded in a tag; don't break it.
@@ -2709,7 +2763,6 @@ class Parser {
$colon = strpos( $str, ':', $i );
if ( $colon === false ) {
# Nothing else interesting
- wfProfileOut( __METHOD__ );
return false;
}
$lt = strpos( $str, '<', $i );
@@ -2718,7 +2771,6 @@ class Parser {
# We found it!
$before = substr( $str, 0, $colon );
$after = substr( $str, $colon + 1 );
- wfProfileOut( __METHOD__ );
return $i;
}
}
@@ -2769,7 +2821,6 @@ class Parser {
$stack--;
if ( $stack < 0 ) {
wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
- wfProfileOut( __METHOD__ );
return false;
}
$state = self::COLON_STATE_TEXT;
@@ -2804,16 +2855,13 @@ class Parser {
}
break;
default:
- wfProfileOut( __METHOD__ );
throw new MWException( "State machine error in " . __METHOD__ );
}
}
if ( $stack > 0 ) {
wfDebug( __METHOD__ . ": Invalid input; not enough close tags (stack $stack, state $state)\n" );
- wfProfileOut( __METHOD__ );
return false;
}
- wfProfileOut( __METHOD__ );
return false;
}
@@ -2845,14 +2893,14 @@ class Parser {
* Some of these require message or data lookups and can be
* expensive to check many times.
*/
- if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
+ if ( Hooks::run( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
if ( isset( $this->mVarCache[$index] ) ) {
return $this->mVarCache[$index];
}
}
$ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
- wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
+ Hooks::run( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
$pageLang = $this->getFunctionLang();
@@ -3129,10 +3177,6 @@ class Parser {
case 'numberofedits':
$value = $pageLang->formatNum( SiteStats::edits() );
break;
- case 'numberofviews':
- global $wgDisableCounters;
- $value = !$wgDisableCounters ? $pageLang->formatNum( SiteStats::views() ) : '';
- break;
case 'currenttimestamp':
$value = wfTimestamp( TS_MW, $ts );
break;
@@ -3164,7 +3208,7 @@ class Parser {
break;
default:
$ret = null;
- wfRunHooks(
+ Hooks::run(
'ParserGetVariableValueSwitch',
array( &$this, &$this->mVarCache, &$index, &$ret, &$frame )
);
@@ -3185,13 +3229,11 @@ class Parser {
* @private
*/
public function initialiseVariables() {
- wfProfileIn( __METHOD__ );
$variableIDs = MagicWord::getVariableIDs();
$substIDs = MagicWord::getSubstIDs();
$this->mVariables = new MagicWordArray( $variableIDs );
$this->mSubstWords = new MagicWordArray( $substIDs );
- wfProfileOut( __METHOD__ );
}
/**
@@ -3266,7 +3308,6 @@ class Parser {
if ( strlen( $text ) < 1 || strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) {
return $text;
}
- wfProfileIn( __METHOD__ );
if ( $frame === false ) {
$frame = $this->getPreprocessor()->newFrame();
@@ -3280,7 +3321,6 @@ class Parser {
$flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
$text = $frame->expand( $dom, $flags );
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -3358,8 +3398,6 @@ class Parser {
* @return string The text of the template
*/
public function braceSubstitution( $piece, $frame ) {
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-setup' );
// Flags
@@ -3392,12 +3430,10 @@ class Parser {
# @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' );
- $titleProfileIn = null; // profile templates
+ $profileSection = null; // profile templates
# SUBST
- wfProfileIn( __METHOD__ . '-modifiers' );
if ( !$found ) {
$substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
@@ -3454,11 +3490,9 @@ class Parser {
$forceRawInterwiki = true;
}
}
- wfProfileOut( __METHOD__ . '-modifiers' );
# Parser functions
if ( !$found ) {
- wfProfileIn( __METHOD__ . '-pfunc' );
$colonPos = strpos( $part1, ':' );
if ( $colonPos !== false ) {
@@ -3470,8 +3504,6 @@ class Parser {
try {
$result = $this->callParserFunction( $frame, $func, $funcArgs );
} catch ( Exception $ex ) {
- wfProfileOut( __METHOD__ . '-pfunc' );
- wfProfileOut( __METHOD__ );
throw $ex;
}
@@ -3480,7 +3512,6 @@ class Parser {
# here.
extract( $result );
}
- wfProfileOut( __METHOD__ . '-pfunc' );
}
# Finish mangling title and then check for loops.
@@ -3515,12 +3546,7 @@ class Parser {
# Load from database
if ( !$found && $title ) {
- if ( !Profiler::instance()->isPersistent() ) {
- # Too many unique items can kill profiling DBs/collectors
- $titleProfileIn = __METHOD__ . "-title-" . $title->getPrefixedDBkey();
- wfProfileIn( $titleProfileIn ); // template in
- }
- wfProfileIn( __METHOD__ . '-loadtpl' );
+ $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
if ( !$title->isExternal() ) {
if ( $title->isSpecialPage()
&& $this->mOptions->getAllowSpecialInclusion()
@@ -3594,17 +3620,15 @@ class Parser {
. '</span>';
wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
}
- wfProfileOut( __METHOD__ . '-loadtpl' );
}
# If we haven't found text to substitute by now, we're done
# Recover the source wikitext and return it
if ( !$found ) {
$text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
- if ( $titleProfileIn ) {
- wfProfileOut( $titleProfileIn ); // template out
+ if ( $profileSection ) {
+ $this->mProfiler->scopedProfileOut( $profileSection );
}
- wfProfileOut( __METHOD__ );
return array( 'object' => $text );
}
@@ -3628,8 +3652,8 @@ class Parser {
$isLocalObj = false;
}
- if ( $titleProfileIn ) {
- wfProfileOut( $titleProfileIn ); // template out
+ if ( $profileSection ) {
+ $this->mProfiler->scopedProfileOut( $profileSection );
}
# Replace raw HTML by a placeholder
@@ -3670,7 +3694,6 @@ class Parser {
$ret = array( 'text' => $text );
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -3696,7 +3719,6 @@ class Parser {
public function callParserFunction( $frame, $function, array $args = array() ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
# Case sensitive functions
if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
@@ -3707,23 +3729,19 @@ class Parser {
if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
$function = $this->mFunctionSynonyms[0][$function];
} else {
- wfProfileOut( __METHOD__ );
return array( 'found' => false );
}
}
- wfProfileIn( __METHOD__ . '-pfunc-' . $function );
list( $callback, $flags ) = $this->mFunctionHooks[$function];
# Workaround for PHP bug 35229 and similar
if ( !is_callable( $callback ) ) {
- wfProfileOut( __METHOD__ . '-pfunc-' . $function );
- wfProfileOut( __METHOD__ );
throw new MWException( "Tag hook for $function is not callable\n" );
}
$allArgs = array( &$this );
- if ( $flags & SFH_OBJECT_ARGS ) {
+ if ( $flags & self::SFH_OBJECT_ARGS ) {
# Convert arguments to PPNodes and collect for appending to $allArgs
$funcArgs = array();
foreach ( $args as $k => $v ) {
@@ -3783,8 +3801,6 @@ class Parser {
$result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
$result['isChildObj'] = true;
}
- wfProfileOut( __METHOD__ . '-pfunc-' . $function );
- wfProfileOut( __METHOD__ );
return $result;
}
@@ -3830,6 +3846,44 @@ class Parser {
}
/**
+ * Fetch the current revision of a given title. Note that the revision
+ * (and even the title) may not exist in the database, so everything
+ * contributing to the output of the parser should use this method
+ * where possible, rather than getting the revisions themselves. This
+ * method also caches its results, so using it benefits performance.
+ *
+ * @since 1.24
+ * @param Title $title
+ * @return Revision
+ */
+ public function fetchCurrentRevisionOfTitle( $title ) {
+ $cacheKey = $title->getPrefixedDBkey();
+ if ( !$this->currentRevisionCache ) {
+ $this->currentRevisionCache = new MapCacheLRU( 100 );
+ }
+ if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
+ $this->currentRevisionCache->set( $cacheKey,
+ // Defaults to Parser::statelessFetchRevision()
+ call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
+ );
+ }
+ return $this->currentRevisionCache->get( $cacheKey );
+ }
+
+ /**
+ * Wrapper around Revision::newFromTitle to allow passing additional parameters
+ * without passing them on to it.
+ *
+ * @since 1.24
+ * @param Title $title
+ * @param Parser|bool $parser
+ * @return Revision
+ */
+ public static function statelessFetchRevision( $title, $parser = false ) {
+ return Revision::newFromTitle( $title );
+ }
+
+ /**
* Fetch the unparsed text of a template and register a reference to it.
* @param Title $title
* @return array ( string or false, Title )
@@ -3881,7 +3935,7 @@ class Parser {
for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
# Give extensions a chance to select the revision instead
$id = false; # Assume current
- wfRunHooks( 'BeforeParserFetchTemplateAndtitle',
+ Hooks::run( 'BeforeParserFetchTemplateAndtitle',
array( $parser, $title, &$skip, &$id ) );
if ( $skip ) {
@@ -3894,9 +3948,13 @@ class Parser {
break;
}
# Get the revision
- $rev = $id
- ? Revision::newFromId( $id )
- : Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
+ if ( $id ) {
+ $rev = Revision::newFromId( $id );
+ } elseif ( $parser ) {
+ $rev = $parser->fetchCurrentRevisionOfTitle( $title );
+ } else {
+ $rev = Revision::newFromTitle( $title );
+ }
$rev_id = $rev ? $rev->getId() : 0;
# If there is no current revision, there is no page
if ( $id === false && !$rev ) {
@@ -4041,7 +4099,7 @@ class Parser {
return $obj->tc_contents;
}
- $req = MWHttpRequest::factory( $url );
+ $req = MWHttpRequest::factory( $url, array(), __METHOD__ );
$status = $req->execute(); // Status object
if ( $status->isOK() ) {
$text = $req->getContent();
@@ -4072,7 +4130,6 @@ class Parser {
* @return array
*/
public function argSubstitution( $piece, $frame ) {
- wfProfileIn( __METHOD__ );
$error = false;
$parts = $piece['parts'];
@@ -4107,7 +4164,6 @@ class Parser {
$ret = array( 'text' => $text );
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -4238,7 +4294,6 @@ class Parser {
* @return string
*/
public function doDoubleUnderscore( $text ) {
- wfProfileIn( __METHOD__ );
# The position of __TOC__ needs to be recorded
$mw = MagicWord::get( 'toc' );
@@ -4286,45 +4341,16 @@ class Parser {
$this->mOutput->setProperty( $key, '' );
}
- wfProfileOut( __METHOD__ );
return $text;
}
/**
- * Add a tracking category, getting the title from a system message,
- * or print a debug message if the title is invalid.
- *
- * Please add any message that you use with this function to
- * $wgTrackingCategories. That way they will be listed on
- * Special:TrackingCategories.
- *
+ * @see ParserOutput::addTrackingCategory()
* @param string $msg Message key
* @return bool Whether the addition was successful
*/
public function addTrackingCategory( $msg ) {
- if ( $this->mTitle->getNamespace() === NS_SPECIAL ) {
- wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
- return false;
- }
- // Important to parse with correct title (bug 31469)
- $cat = wfMessage( $msg )
- ->title( $this->getTitle() )
- ->inContentLanguage()
- ->text();
-
- # Allow tracking categories to be disabled by setting them to "-"
- if ( $cat === '-' ) {
- return false;
- }
-
- $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
- if ( $containerCategory ) {
- $this->mOutput->addCategory( $containerCategory->getDBkey(), $this->getDefaultSort() );
- return true;
- } else {
- wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
- return false;
- }
+ return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
}
/**
@@ -4361,7 +4387,7 @@ class Parser {
# 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',
+ '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
$text,
$matches
);
@@ -4508,14 +4534,15 @@ class Parser {
# * <sup> and <sub> (bug 8393)
# * <i> (bug 26375)
# * <b> (r105284)
+ # * <bdi> (bug 72884)
# * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
#
# 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))(?: .*?)?' . '>#'
+ '#<(?!/?(span|sup|sub|bdi|i|b)(?: [^>]*)?>).*?>#',
+ '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b))(?: .*?)?>#'
),
array( '', '<$1>' ),
$safeHeadline
@@ -4523,7 +4550,7 @@ class Parser {
$tocline = trim( $tocline );
# For the anchor, strip out HTML-y stuff period
- $safeHeadline = preg_replace( '/<.*?' . '>/', '', $safeHeadline );
+ $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
$safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
# Save headline for section edit hint before it's escaped
@@ -4556,7 +4583,7 @@ class Parser {
# HTML names must be case-insensitively unique (bug 10721).
# This does not apply to Unicode characters per
- # http://dev.w3.org/html5/spec/infrastructure.html#case-sensitivity-and-string-comparison
+ # http://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
# @todo FIXME: We may be changing them depending on the current locale.
$arrayKey = strtolower( $safeHeadline );
if ( $legacyHeadline === false ) {
@@ -4565,16 +4592,22 @@ class Parser {
$legacyArrayKey = strtolower( $legacyHeadline );
}
- # count how many in assoc. array so we can track dupes in anchors
+ # Create the anchor for linking from the TOC to the section
+ $anchor = $safeHeadline;
+ $legacyAnchor = $legacyHeadline;
if ( isset( $refers[$arrayKey] ) ) {
- $refers[$arrayKey]++;
+ for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
+ $anchor .= "_$i";
+ $refers["${arrayKey}_$i"] = true;
} else {
- $refers[$arrayKey] = 1;
+ $refers[$arrayKey] = true;
}
- if ( isset( $refers[$legacyArrayKey] ) ) {
- $refers[$legacyArrayKey]++;
+ if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
+ for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
+ $legacyAnchor .= "_$i";
+ $refers["${legacyArrayKey}_$i"] = true;
} else {
- $refers[$legacyArrayKey] = 1;
+ $refers[$legacyArrayKey] = true;
}
# Don't number the heading if it is the only one (looks silly)
@@ -4587,15 +4620,6 @@ class Parser {
) . ' ' . $headline;
}
- # Create the anchor for linking from the TOC to the section
- $anchor = $safeHeadline;
- $legacyAnchor = $legacyHeadline;
- if ( $refers[$arrayKey] > 1 ) {
- $anchor .= '_' . $refers[$arrayKey];
- }
- if ( $legacyHeadline !== false && $refers[$legacyArrayKey] > 1 ) {
- $legacyAnchor .= '_' . $refers[$legacyArrayKey];
- }
if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
$toc .= Linker::tocLine( $anchor, $tocline,
$numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
@@ -4691,7 +4715,7 @@ class Parser {
}
# split up and insert constructed headlines
- $blocks = preg_split( '/<H[1-6].*?' . '>[\s\S]*?<\/H[1-6]>/i', $text );
+ $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
$i = 0;
// build an array of document sections
@@ -4714,7 +4738,7 @@ class Parser {
* &$sectionContent : ref to the content of the section
* $showEditLinks : boolean describing whether this section has an edit link
*/
- wfRunHooks( 'ParserSectionCreate', array( $this, $i, &$sections[$i], $showEditLink ) );
+ Hooks::run( 'ParserSectionCreate', array( $this, $i, &$sections[$i], $showEditLink ) );
$i++;
}
@@ -4736,7 +4760,7 @@ class Parser {
/**
* Transform wiki markup when saving a page by doing "\r\n" -> "\n"
- * conversion, substitting signatures, {{subst:}} templates, etc.
+ * conversion, substituting signatures, {{subst:}} templates, etc.
*
* @param string $text The text to transform
* @param Title $title The Title object for the current article
@@ -4756,6 +4780,7 @@ class Parser {
$pairs = array(
"\r\n" => "\n",
+ "\r" => "\n",
);
$text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
if ( $options->getPreSaveTransform() ) {
@@ -4920,7 +4945,7 @@ class Parser {
/**
* Clean up signature text
*
- * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures @see cleanSigInSig
+ * 1) Strip 3, 4 or 5 tildes out of signatures @see cleanSigInSig
* 2) Substitute all transclusions
*
* @param string $text
@@ -4959,7 +4984,7 @@ class Parser {
}
/**
- * Strip ~~~, ~~~~ and ~~~~~ out of signatures
+ * Strip 3, 4 or 5 tildes out of signatures.
*
* @param string $text
* @return string Signature text with /~{3,5}/ removed
@@ -5018,7 +5043,6 @@ class Parser {
}
$executing = true;
- wfProfileIn( __METHOD__ );
if ( !$title ) {
global $wgTitle;
$title = $wgTitle;
@@ -5027,7 +5051,6 @@ class Parser {
$text = $this->preprocess( $text, $title, $options );
$executing = false;
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -5111,7 +5134,7 @@ class Parser {
* The callback function should have the form:
* function myParserFunction( &$parser, $arg1, $arg2, $arg3 ) { ... }
*
- * Or with SFH_OBJECT_ARGS:
+ * Or with Parser::SFH_OBJECT_ARGS:
* function myParserFunction( $parser, $frame, $args ) { ... }
*
* The callback may either return the text result of the function, or an array with the text
@@ -5125,10 +5148,10 @@ class Parser {
* @param string $id The magic word ID
* @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:...}}
+ * Parser::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
- * allows for conditional expansion of the parse tree, allowing you to eliminate dead
+ * Parser::SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text.
+ * This allows for conditional expansion of the parse tree, allowing you to eliminate dead
* branches and thus speed up parsing. It is also possible to analyse the parse tree of
* the arguments, and to control the way they are expanded.
*
@@ -5170,7 +5193,7 @@ class Parser {
$syn = $wgContLang->lc( $syn );
}
# Add leading hash
- if ( !( $flags & SFH_NO_HASH ) ) {
+ if ( !( $flags & self::SFH_NO_HASH ) ) {
$syn = '#' . $syn;
}
# Remove trailing colon
@@ -5224,11 +5247,9 @@ class Parser {
*
* @param string $text
* @param int $options
- *
- * @return array Array of link CSS classes, indexed by PDBK.
*/
public function replaceLinkHolders( &$text, $options = 0 ) {
- return $this->mLinkHolders->replace( $text );
+ $this->mLinkHolders->replace( $text );
}
/**
@@ -5256,7 +5277,6 @@ class Parser {
* @return string HTML
*/
public function renderImageGallery( $text, $params ) {
- wfProfileIn( __METHOD__ );
$mode = false;
if ( isset( $params['mode'] ) ) {
@@ -5265,7 +5285,7 @@ class Parser {
try {
$ig = ImageGalleryBase::factory( $mode );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// If invalid type set, fallback to default.
$ig = ImageGalleryBase::factory( false );
}
@@ -5299,7 +5319,7 @@ class Parser {
}
$ig->setAdditionalOptions( $params );
- wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
+ Hooks::run( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
$lines = StringUtils::explode( "\n", $text );
foreach ( $lines as $line ) {
@@ -5326,13 +5346,12 @@ class Parser {
# file (which potentially could be of a different type and have different handler).
$options = array();
$descQuery = false;
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ Hooks::run( 'BeforeParserFetchFileAndTitle',
array( $this, $title, &$options, &$descQuery ) );
# Don't register it now, as ImageGallery does that later.
$file = $this->fetchFileNoRegister( $title, $options );
$handler = $file ? $file->getHandler() : false;
- wfProfileIn( __METHOD__ . '-getMagicWord' );
$paramMap = array(
'img_alt' => 'gallery-internal-alt',
'img_link' => 'gallery-internal-link',
@@ -5345,7 +5364,6 @@ class Parser {
}
$mwArray = new MagicWordArray( array_keys( $paramMap ) );
- wfProfileOut( __METHOD__ . '-getMagicWord' );
$label = '';
$alt = '';
@@ -5407,13 +5425,12 @@ class Parser {
$ig->add( $title, $label, $alt, $link, $handlerOptions );
}
$html = $ig->toHTML();
- wfRunHooks( 'AfterParserFetchFileAndTitle', array( $this, $ig, &$html ) );
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'AfterParserFetchFileAndTitle', array( $this, $ig, &$html ) );
return $html;
}
/**
- * @param string $handler
+ * @param MediaHandler $handler
* @return array
*/
public function getImageParams( $handler ) {
@@ -5496,7 +5513,7 @@ class Parser {
# Give extensions a chance to select the file revision for us
$options = array();
$descQuery = false;
- wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ Hooks::run( 'BeforeParserFetchFileAndTitle',
array( $this, $title, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
@@ -5660,7 +5677,7 @@ class Parser {
$params['frame']['title'] = $this->stripAltText( $caption, $holders );
}
- wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params, $this ) );
+ Hooks::run( 'ParserMakeImageParams', array( $title, $file, &$params, $this ) );
# Linker does the rest
$time = isset( $options['time'] ) ? $options['time'] : false;
@@ -5969,7 +5986,19 @@ class Parser {
return null;
}
- $this->mRevisionObject = Revision::newFromId( $this->mRevisionId );
+ $rev = call_user_func(
+ $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
+ );
+
+ # If the parse is for a new revision, then the callback should have
+ # already been set to force the object and should match mRevisionId.
+ # If not, try to fetch by mRevisionId for sanity.
+ if ( $rev && $rev->getId() != $this->mRevisionId ) {
+ $rev = Revision::newFromId( $this->mRevisionId );
+ }
+
+ $this->mRevisionObject = $rev;
+
return $this->mRevisionObject;
}
@@ -5980,8 +6009,6 @@ class Parser {
*/
public function getRevisionTimestamp() {
if ( is_null( $this->mRevisionTimestamp ) ) {
- wfProfileIn( __METHOD__ );
-
global $wgContLang;
$revObject = $this->getRevisionObject();
@@ -5995,7 +6022,6 @@ class Parser {
# it needs to be consistent for all visitors.
$this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
- wfProfileOut( __METHOD__ );
}
return $this->mRevisionTimestamp;
}
@@ -6250,14 +6276,12 @@ class Parser {
* @return array
*/
public function serializeHalfParsedText( $text ) {
- wfProfileIn( __METHOD__ );
$data = array(
'text' => $text,
'version' => self::HALF_PARSED_VERSION,
'stripState' => $this->mStripState->getSubState( $text ),
'linkHolders' => $this->mLinkHolders->getSubArray( $text )
);
- wfProfileOut( __METHOD__ );
return $data;
}
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index 79523003..bc8e4a69 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -184,12 +184,10 @@ class ParserCache {
*/
public function get( $article, $popts, $useOutdated = false ) {
global $wgCacheEpoch;
- wfProfileIn( __METHOD__ );
$canCache = $article->checkTouched();
if ( !$canCache ) {
// It's a redirect now
- wfProfileOut( __METHOD__ );
return false;
}
@@ -198,7 +196,6 @@ class ParserCache {
$parserOutputKey = $this->getKey( $article, $popts, $useOutdated );
if ( $parserOutputKey === false ) {
wfIncrStats( 'pcache_miss_absent' );
- wfProfileOut( __METHOD__ );
return false;
}
@@ -206,7 +203,6 @@ class ParserCache {
if ( !$value ) {
wfDebug( "ParserOutput cache miss.\n" );
wfIncrStats( "pcache_miss_absent" );
- wfProfileOut( __METHOD__ );
return false;
}
@@ -233,7 +229,6 @@ class ParserCache {
wfIncrStats( "pcache_hit" );
}
- wfProfileOut( __METHOD__ );
return $value;
}
@@ -262,8 +257,6 @@ class ParserCache {
$optionsKey->setCacheRevisionId( $revId );
$parserOutput->setCacheRevisionId( $revId );
- $optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
-
$parserOutputKey = $this->getParserOutputKey( $page,
$popts->optionsHash( $optionsKey->mUsedOptions, $page->getTitle() ) );
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index 7e4059b8..100656d1 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -25,7 +25,7 @@
* @brief Set options of the Parser
*
* All member variables are supposed to be private in theory, although in
- * practise this is not the case.
+ * practice this is not the case.
*
* @ingroup Parser
*/
@@ -117,6 +117,12 @@ class ParserOptions {
public $mRemoveComments = true;
/**
+ * Callback for current revision fetching. Used as first argument to call_user_func().
+ */
+ public $mCurrentRevisionCallback =
+ array( 'Parser', 'statelessFetchRevision' );
+
+ /**
* Callback for template fetching. Used as first argument to call_user_func().
*/
public $mTemplateCallback =
@@ -139,9 +145,7 @@ class ParserOptions {
/**
* Clean up signature texts?
- *
- * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures
- * 2) Substitute all transclusions
+ * @see Parser::cleanSig
*/
public $mCleanSignatures;
@@ -289,6 +293,11 @@ class ParserOptions {
return $this->mRemoveComments;
}
+ /* @since 1.24 */
+ public function getCurrentRevisionCallback() {
+ return $this->mCurrentRevisionCallback;
+ }
+
public function getTemplateCallback() {
return $this->mTemplateCallback;
}
@@ -462,6 +471,11 @@ class ParserOptions {
return wfSetVar( $this->mRemoveComments, $x );
}
+ /* @since 1.24 */
+ public function setCurrentRevisionCallback( $x ) {
+ return wfSetVar( $this->mCurrentRevisionCallback, $x );
+ }
+
public function setTemplateCallback( $x ) {
return wfSetVar( $this->mTemplateCallback, $x );
}
@@ -623,8 +637,7 @@ class ParserOptions {
$wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
$wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion;
- wfProfileIn( __METHOD__ );
-
+ // *UPDATE* ParserOptions::matches() if any of this changes as needed
$this->mInterwikiMagic = $wgInterwikiMagic;
$this->mAllowExternalImages = $wgAllowExternalImages;
$this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
@@ -647,7 +660,33 @@ class ParserOptions {
$this->mStubThreshold = $user->getStubThreshold();
$this->mUserLang = $lang;
- wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Check if these options match that of another options set
+ *
+ * This ignores report limit settings that only affect HTML comments
+ *
+ * @param ParserOptions $other
+ * @return bool
+ * @since 1.25
+ */
+ public function matches( ParserOptions $other ) {
+ $fields = array_keys( get_class_vars( __CLASS__ ) );
+ $fields = array_diff( $fields, array(
+ 'mEnableLimitReport', // only effects HTML comments
+ 'onAccessCallback', // only used for ParserOutput option tracking
+ ) );
+ foreach ( $fields as $field ) {
+ if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
+ return false;
+ }
+ }
+ // Check the object and lazy-loaded options
+ return (
+ $this->mUserLang->getCode() === $other->mUserLang->getCode() &&
+ $this->getDateFormat() === $other->getDateFormat()
+ );
}
/**
@@ -768,11 +807,53 @@ 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, $this->getUser(), &$forOptions ) );
+ Hooks::run( 'PageRenderingHash', array( &$confstr, $this->getUser(), &$forOptions ) );
// Make it a valid memcached key fragment
$confstr = str_replace( ' ', '_', $confstr );
return $confstr;
}
+
+ /**
+ * Sets a hook to force that a page exists, and sets a current revision callback to return a
+ * revision with custom content when the current revision of the page is requested.
+ *
+ * @since 1.25
+ * @param Title $title
+ * @param Content $content
+ * @param User $user The user that the fake revision is attributed to
+ * @return ScopedCallback to unset the hook
+ */
+ public function setupFakeRevision( $title, $content, $user ) {
+ $oldCallback = $this->setCurrentRevisionCallback( function ( $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback ) {
+ if ( $titleToCheck->equals( $title ) ) {
+ return new Revision( array(
+ 'page' => $title->getArticleID(),
+ 'user_text' => $user->getName(),
+ 'user' => $user->getId(),
+ 'parent_id' => $title->getLatestRevId(),
+ 'title' => $title,
+ 'content' => $content
+ ) );
+ } else {
+ return call_user_func( $oldCallback, $titleToCheck, $parser );
+ }
+ } );
+ global $wgHooks;
+ $wgHooks['TitleExists'][] =
+ function ( $titleToCheck, &$exists ) use ( $title ) {
+ if ( $titleToCheck->equals( $title ) ) {
+ $exists = true;
+ }
+ };
+ end( $wgHooks['TitleExists'] );
+ $key = key( $wgHooks['TitleExists'] );
+ LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() );
+ return new ScopedCallback( function () use ( $title, $key ) {
+ global $wgHooks;
+ unset( $wgHooks['TitleExists'][$key] );
+ LinkCache::singleton()->clearLink( $title );
+ } );
+ }
}
diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php
index 5037ce18..65b527c8 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -25,6 +25,7 @@ class ParserOutput extends CacheTime {
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
+ $mIndicators = array(), # Page status indicators, usually displayed in top-right corner
$mTitleText, # title text of the chosen language variant
$mLinks = array(), # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken.
$mTemplates = array(), # 2-D map of NS/DBK to ID for the template references. ID=zero for broken.
@@ -52,27 +53,25 @@ class ParserOutput extends CacheTime {
$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
+ private $mFlags = array(); # Generic flags
const EDITSECTION_REGEX =
'#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
public function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(),
- $containsOldMagic = false, $titletext = ''
+ $unused = false, $titletext = ''
) {
$this->mText = $text;
$this->mLanguageLinks = $languageLinks;
$this->mCategories = $categoryLinks;
- $this->mContainsOldMagic = $containsOldMagic;
$this->mTitleText = $titletext;
}
public function getText() {
- wfProfileIn( __METHOD__ );
$text = $this->mText;
if ( $this->mEditSectionTokens ) {
$text = preg_replace_callback(
@@ -110,7 +109,6 @@ class ParserOutput extends CacheTime {
$text
);
}
- wfProfileOut( __METHOD__ );
return $text;
}
@@ -130,6 +128,13 @@ class ParserOutput extends CacheTime {
return $this->mCategories;
}
+ /**
+ * @since 1.25
+ */
+ public function getIndicators() {
+ return $this->mIndicators;
+ }
+
public function getTitleText() {
return $this->mTitleText;
}
@@ -267,6 +272,13 @@ class ParserOutput extends CacheTime {
$this->mCategories[$c] = $sort;
}
+ /**
+ * @since 1.25
+ */
+ public function setIndicator( $id, $content ) {
+ $this->mIndicators[$id] = $content;
+ }
+
public function addLanguageLink( $t ) {
$this->mLanguageLinks[] = $t;
}
@@ -472,6 +484,47 @@ class ParserOutput extends CacheTime {
}
/**
+ * Add a tracking category, getting the title from a system message,
+ * or print a debug message if the title is invalid.
+ *
+ * Any message used with this function should be registered so it will
+ * show up on Special:TrackingCategories. Core messages should be added
+ * to SpecialTrackingCategories::$coreTrackingCategories, and extensions
+ * should add to "TrackingCategories" in their extension.json.
+ *
+ * @param string $msg Message key
+ * @param Title $title title of the page which is being tracked
+ * @return bool Whether the addition was successful
+ * @since 1.25
+ */
+ public function addTrackingCategory( $msg, $title ) {
+ if ( $title->getNamespace() === NS_SPECIAL ) {
+ wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
+ return false;
+ }
+
+ // Important to parse with correct title (bug 31469)
+ $cat = wfMessage( $msg )
+ ->title( $title )
+ ->inContentLanguage()
+ ->text();
+
+ # Allow tracking categories to be disabled by setting them to "-"
+ if ( $cat === '-' ) {
+ return false;
+ }
+
+ $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
+ if ( $containerCategory ) {
+ $this->addCategory( $containerCategory->getDBkey(), $this->getProperty( 'defaultsort' ) ?: '' );
+ return true;
+ } else {
+ wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
+ return false;
+ }
+ }
+
+ /**
* Override the title to be used for display
* -- this is assumed to have been validated
* (check equal normalisation, etc.)
@@ -622,43 +675,57 @@ class ParserOutput extends CacheTime {
}
/**
- * 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.
+ * @deprecated since 1.25. Instead, store any relevant data using setExtensionData,
+ * and implement Content::getSecondaryDataUpdates() if possible, or use the
+ * 'SecondaryDataUpdates' hook to construct the necessary update objects.
*
- * @since 1.20
+ * @note Hard deprecation and removal without long deprecation period, since there are no
+ * known users, but known conceptual issues.
+ *
+ * @todo remove in 1.26
*
* @param DataUpdate $update
+ *
+ * @throws MWException
*/
public function addSecondaryDataUpdate( DataUpdate $update ) {
- $this->mSecondaryDataUpdates[] = $update;
+ wfDeprecated( __METHOD__, '1.25' );
+ throw new MWException( 'ParserOutput::addSecondaryDataUpdate() is no longer supported. Override Content::getSecondaryDataUpdates() or use the SecondaryDataUpdates hook instead.' );
}
/**
- * Returns any DataUpdate jobs to be executed in order to store secondary information
- * extracted from the page's content, including a LinksUpdate object for all links stored in
- * this ParserOutput object.
+ * @deprecated since 1.25.
*
- * @note Avoid using this method directly, use ContentHandler::getSecondaryDataUpdates()
- * instead! The content handler may provide additional update objects.
+ * @note Hard deprecation and removal without long deprecation period, since there are no
+ * known users, but known conceptual issues.
*
- * @since 1.20
+ * @todo remove in 1.26
*
- * @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 bool false (since 1.25)
+ */
+ public function hasCustomDataUpdates() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return false;
+ }
+
+ /**
+ * @deprecated since 1.25. Instead, store any relevant data using setExtensionData,
+ * and implement Content::getSecondaryDataUpdates() if possible, or use the
+ * 'SecondaryDataUpdates' hook to construct the necessary update objects.
+ *
+ * @note Hard deprecation and removal without long deprecation period, since there are no
+ * known users, but known conceptual issues.
+ *
+ * @todo remove in 1.26
+ *
+ * @param Title $title
+ * @param bool $recursive
*
* @return array An array of instances of DataUpdate
*/
public function getSecondaryDataUpdates( Title $title = null, $recursive = true ) {
- if ( is_null( $title ) ) {
- $title = Title::newFromText( $this->getTitleText() );
- }
-
- $linksUpdate = new LinksUpdate( $title, $this, $recursive );
-
- return array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) );
+ wfDeprecated( __METHOD__, '1.25' );
+ return array();
}
/**
@@ -795,6 +862,22 @@ class ParserOutput extends CacheTime {
}
/**
+ * Check whether the cache TTL was lowered due to dynamic content
+ *
+ * When content is determined by more than hard state (e.g. page edits),
+ * such as template/file transclusions based on the current timestamp or
+ * extension tags that generate lists based on queries, this return true.
+ *
+ * @return bool
+ * @since 1.25
+ */
+ public function hasDynamicContent() {
+ global $wgParserCacheExpireTime;
+
+ return $this->getCacheExpiry() < $wgParserCacheExpireTime;
+ }
+
+ /**
* Get or set the prevent-clickjacking flag
*
* @since 1.24
@@ -806,13 +889,13 @@ class ParserOutput extends CacheTime {
}
/**
- * Save space for for serialization by removing useless values
+ * Save space for serialization by removing useless values
* @return array
*/
public function __sleep() {
return array_diff(
array_keys( get_object_vars( $this ) ),
- array( 'mSecondaryDataUpdates', 'mParseStartTime' )
+ array( 'mParseStartTime' )
);
}
}
diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php
index 2edb79a2..0351f2a8 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -68,6 +68,7 @@ class Preprocessor_DOM implements Preprocessor {
/**
* @param array $values
* @return PPNode_DOM
+ * @throws MWException
*/
public function newPartNodeArray( $values ) {
//NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais)
@@ -85,19 +86,17 @@ class Preprocessor_DOM implements Preprocessor {
$xml .= "</list>";
- wfProfileIn( __METHOD__ . '-loadXML' );
$dom = new DOMDocument();
wfSuppressWarnings();
$result = $dom->loadXML( $xml );
wfRestoreWarnings();
if ( !$result ) {
// Try running the XML through UtfNormal to get rid of invalid characters
- $xml = UtfNormal::cleanUp( $xml );
+ $xml = UtfNormal\Validator::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' );
@@ -149,14 +148,12 @@ class Preprocessor_DOM implements Preprocessor {
* @return PPNode_DOM
*/
public function preprocessToObj( $text, $flags = 0 ) {
- wfProfileIn( __METHOD__ );
global $wgMemc, $wgPreprocessorCacheThreshold;
$xml = false;
$cacheable = ( $wgPreprocessorCacheThreshold !== false
&& strlen( $text ) > $wgPreprocessorCacheThreshold );
if ( $cacheable ) {
- wfProfileIn( __METHOD__ . '-cacheable' );
$cacheKey = wfMemcKey( 'preprocess-xml', md5( $text ), $flags );
$cacheValue = $wgMemc->get( $cacheKey );
@@ -169,11 +166,9 @@ class Preprocessor_DOM implements Preprocessor {
}
}
if ( $xml === false ) {
- wfProfileIn( __METHOD__ . '-cache-miss' );
$xml = $this->preprocessToXml( $text, $flags );
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . $xml;
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
- wfProfileOut( __METHOD__ . '-cache-miss' );
wfDebugLog( "Preprocessor", "Saved preprocessor XML to memcached (key $cacheKey)" );
}
} else {
@@ -186,20 +181,17 @@ class Preprocessor_DOM implements Preprocessor {
$max = $this->parser->mOptions->getMaxGeneratedPPNodeCount();
if ( $this->parser->mGeneratedPPNodeCount > $max ) {
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': generated node count limit exceeded' );
}
- wfProfileIn( __METHOD__ . '-loadXML' );
$dom = new DOMDocument;
wfSuppressWarnings();
$result = $dom->loadXML( $xml );
wfRestoreWarnings();
if ( !$result ) {
// Try running the XML through UtfNormal to get rid of invalid characters
- $xml = UtfNormal::cleanUp( $xml );
+ $xml = UtfNormal\Validator::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 );
@@ -207,14 +199,10 @@ class Preprocessor_DOM implements Preprocessor {
if ( $result ) {
$obj = new PPNode_DOM( $dom->documentElement );
}
- wfProfileOut( __METHOD__ . '-loadXML' );
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
-
if ( !$result ) {
throw new MWException( __METHOD__ . ' generated invalid XML' );
}
@@ -227,7 +215,6 @@ class Preprocessor_DOM implements Preprocessor {
* @return string
*/
public function preprocessToXml( $text, $flags = 0 ) {
- wfProfileIn( __METHOD__ );
$rules = array(
'{' => array(
'end' => '}',
@@ -764,8 +751,6 @@ class Preprocessor_DOM implements Preprocessor {
$stack->rootAccum .= '</root>';
$xml = $stack->rootAccum;
- wfProfileOut( __METHOD__ );
-
return $xml;
}
}
@@ -1043,11 +1028,17 @@ class PPFrame_DOM implements PPFrame {
// Numbered parameter
$index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
$index = $index - $indexOffset;
+ if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$numberedArgs[$index] = $value->item( 0 );
unset( $namedArgs[$index] );
} else {
// Named parameter
$name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
+ if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$namedArgs[$name] = $value->item( 0 );
unset( $numberedArgs[$name] );
}
@@ -1095,7 +1086,6 @@ class PPFrame_DOM implements PPFrame {
);
return '<span class="error">Expansion depth limit exceeded</span>';
}
- wfProfileIn( __METHOD__ );
++$expansionDepth;
if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
$this->parser->mHighestExpansionDepth = $expansionDepth;
@@ -1284,7 +1274,6 @@ class PPFrame_DOM implements PPFrame {
$newIterator = $contextNode->childNodes;
}
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': Invalid parameter type' );
}
@@ -1308,7 +1297,6 @@ class PPFrame_DOM implements PPFrame {
}
}
--$expansionDepth;
- wfProfileOut( __METHOD__ );
return $outStack[0];
}
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index 63763967..af91ad47 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -112,7 +112,6 @@ class Preprocessor_Hash implements Preprocessor {
* @return PPNode_Hash_Tree
*/
public function preprocessToObj( $text, $flags = 0 ) {
- wfProfileIn( __METHOD__ );
// Check cache.
global $wgMemc, $wgPreprocessorCacheThreshold;
@@ -121,7 +120,6 @@ class Preprocessor_Hash implements Preprocessor {
&& strlen( $text ) > $wgPreprocessorCacheThreshold;
if ( $cacheable ) {
- wfProfileIn( __METHOD__ . '-cacheable' );
$cacheKey = wfMemcKey( 'preprocess-hash', md5( $text ), $flags );
$cacheValue = $wgMemc->get( $cacheKey );
@@ -132,12 +130,9 @@ class Preprocessor_Hash implements Preprocessor {
// From the cache
wfDebugLog( "Preprocessor",
"Loaded preprocessor hash from memcached (key $cacheKey)" );
- wfProfileOut( __METHOD__ . '-cacheable' );
- wfProfileOut( __METHOD__ );
return $hash;
}
}
- wfProfileIn( __METHOD__ . '-cache-miss' );
}
$rules = array(
@@ -637,18 +632,12 @@ class Preprocessor_Hash implements Preprocessor {
}
if ( !$node ) {
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cache-miss' );
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': eqpos not found' );
}
if ( $node->name !== 'equals' ) {
if ( $cacheable ) {
- wfProfileOut( __METHOD__ . '-cache-miss' );
- wfProfileOut( __METHOD__ . '-cacheable' );
}
- wfProfileOut( __METHOD__ );
throw new MWException( __METHOD__ . ': eqpos is not equals' );
}
$equalsNode = $node;
@@ -748,12 +737,9 @@ class Preprocessor_Hash implements Preprocessor {
if ( $cacheable ) {
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
- wfProfileOut( __METHOD__ . '-cache-miss' );
- wfProfileOut( __METHOD__ . '-cacheable' );
wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
}
- wfProfileOut( __METHOD__ );
return $rootNode;
}
}
@@ -985,11 +971,17 @@ class PPFrame_Hash implements PPFrame {
if ( $bits['index'] !== '' ) {
// Numbered parameter
$index = $bits['index'] - $indexOffset;
+ if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$numberedArgs[$index] = $bits['value'];
unset( $namedArgs[$index] );
} else {
// Named parameter
$name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
+ if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
+ $this->parser->addTrackingCategory( 'duplicate-args-category' );
+ }
$namedArgs[$name] = $bits['value'];
unset( $numberedArgs[$name] );
}
diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php
index 5d1743e6..51ae42dc 100644
--- a/includes/parser/StripState.php
+++ b/includes/parser/StripState.php
@@ -117,12 +117,10 @@ class StripState {
return $text;
}
- wfProfileIn( __METHOD__ );
$oldType = $this->tempType;
$this->tempType = $type;
$text = preg_replace_callback( $this->regex, array( $this, 'unstripCallback' ), $text );
$this->tempType = $oldType;
- wfProfileOut( __METHOD__ );
return $text;
}
diff --git a/includes/password/PasswordFactory.php b/includes/password/PasswordFactory.php
index 3b4ebb1a..86a3fefd 100644
--- a/includes/password/PasswordFactory.php
+++ b/includes/password/PasswordFactory.php
@@ -69,6 +69,15 @@ final class PasswordFactory {
}
/**
+ * Get the default password type
+ *
+ * @return string
+ */
+ public function getDefaultType() {
+ return $this->default;
+ }
+
+ /**
* Initialize the internal static variables using the global variables
*
* @param Config $config Configuration object to load data from
@@ -141,11 +150,15 @@ final class PasswordFactory {
* 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 string|null $password Plaintext password, or null for an invalid password
* @param Password|null $existing Optional existing hash to get options from
* @return Password
*/
public function newFromPlaintext( $password, Password $existing = null ) {
+ if ( $password === null ) {
+ return new InvalidPassword( $this, array( 'type' => '' ), null );
+ }
+
if ( $existing === null ) {
$config = $this->types[$this->default];
$obj = new $config['class']( $this, $config );
diff --git a/includes/poolcounter/PoolCounter.php b/includes/poolcounter/PoolCounter.php
index e77ffd7c..5692d731 100644
--- a/includes/poolcounter/PoolCounter.php
+++ b/includes/poolcounter/PoolCounter.php
@@ -34,7 +34,10 @@
* 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.
+ * of workers that may be concurrently performing such single task. Only one
+ * key can be locked by any PoolCounter instance of a process, except for keys
+ * that start with "nowait:". However, only 0 timeouts (non-blocking requests)
+ * can be used with "nowait:" keys.
*
* By default PoolCounter_Stub is used, which provides no locking. You
* can get a useful one in the PoolCounter extension.
@@ -68,6 +71,15 @@ abstract class PoolCounter {
protected $timeout;
/**
+ * @var boolean Whether the key is a "might wait" key
+ */
+ private $isMightWaitKey;
+ /**
+ * @var boolean Whether this process holds a "might wait" lock key
+ */
+ private static $acquiredMightWaitKey = 0;
+
+ /**
* @param array $conf
* @param string $type
* @param string $key
@@ -84,6 +96,7 @@ abstract class PoolCounter {
$key = $this->hashKeyIntoSlots( $key, $this->slots );
}
$this->key = $key;
+ $this->isMightWaitKey = !preg_match( '/^nowait:/', $this->key );
}
/**
@@ -137,6 +150,48 @@ abstract class PoolCounter {
abstract public function release();
/**
+ * Checks that the lock request is sane.
+ * @return Status - good for sane requests fatal for insane
+ * @since 1.25
+ */
+ final protected function precheckAcquire() {
+ if ( $this->isMightWaitKey ) {
+ if ( self::$acquiredMightWaitKey ) {
+ /*
+ * The poolcounter itself is quite happy to allow you to wait
+ * on another lock while you have a lock you waited on already
+ * but we think that it is unlikely to be a good idea. So we
+ * made it an error. If you are _really_ _really_ sure it is a
+ * good idea then feel free to implement an unsafe flag or
+ * something.
+ */
+ return Status::newFatal( 'poolcounter-usage-error',
+ 'You may only aquire a single non-nowait lock.' );
+ }
+ } elseif ( $this->timeout !== 0 ) {
+ return Status::newFatal( 'poolcounter-usage-error',
+ 'Locks starting in nowait: must have 0 timeout.' );
+ }
+ return Status::newGood();
+ }
+
+ /**
+ * Update any lock tracking information when the lock is acquired
+ * @since 1.25
+ */
+ final protected function onAcquire() {
+ self::$acquiredMightWaitKey |= $this->isMightWaitKey;
+ }
+
+ /**
+ * Update any lock tracking information when the lock is released
+ * @since 1.25
+ */
+ final protected function onRelease() {
+ self::$acquiredMightWaitKey &= !$this->isMightWaitKey;
+ }
+
+ /**
* 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
diff --git a/includes/poolcounter/PoolCounterRedis.php b/includes/poolcounter/PoolCounterRedis.php
index d609f614..98797a30 100644
--- a/includes/poolcounter/PoolCounterRedis.php
+++ b/includes/poolcounter/PoolCounterRedis.php
@@ -121,19 +121,26 @@ class PoolCounterRedis extends PoolCounter {
}
function acquireForMe() {
- $section = new ProfileSection( __METHOD__ );
+
+ $status = $this->precheckAcquire();
+ if ( !$status->isGood() ) {
+ return $status;
+ }
return $this->waitForSlotOrNotif( self::AWAKE_ONE );
}
function acquireForAnyone() {
- $section = new ProfileSection( __METHOD__ );
+
+ $status = $this->precheckAcquire();
+ if ( !$status->isGood() ) {
+ return $status;
+ }
return $this->waitForSlotOrNotif( self::AWAKE_ALL );
}
function release() {
- $section = new ProfileSection( __METHOD__ );
if ( $this->slot === null ) {
return Status::newGood( PoolCounter::NOT_LOCKED ); // not locked
@@ -207,6 +214,8 @@ LUA;
$this->onRelease = null;
unset( self::$active[$this->session] );
+ $this->onRelease();
+
return Status::newGood( PoolCounter::RELEASED );
}
@@ -266,6 +275,8 @@ LUA;
self::$active[$this->session] = $this;
}
+ $this->onAcquire();
+
return Status::newGood( $slot === 'w' ? PoolCounter::DONE : PoolCounter::LOCKED );
}
diff --git a/includes/poolcounter/PoolWorkArticleView.php b/includes/poolcounter/PoolWorkArticleView.php
index 5e7e3912..a702d2e8 100644
--- a/includes/poolcounter/PoolWorkArticleView.php
+++ b/includes/poolcounter/PoolWorkArticleView.php
@@ -67,7 +67,8 @@ class PoolWorkArticleView extends PoolCounterWork {
$this->parserOptions = $parserOptions;
$this->content = $content;
$this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
- parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
+ $keyPrefix = $this->cacheKey ?: wfMemcKey( 'articleview', 'missingcachekey' );
+ parent::__construct( 'ArticleView', $keyPrefix . ':revid:' . $revid );
}
/**
@@ -153,12 +154,12 @@ class PoolWorkArticleView extends PoolCounterWork {
// 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() ) {
+ if ( !$this->parserOutput->isCacheable() ) {
$wgUseFileCache = false;
}
if ( $isCurrent ) {
- $this->page->doCascadeProtectionUpdates( $this->parserOutput );
+ $this->page->triggerOpportunisticLinksUpdate( $this->parserOutput );
}
return true;
diff --git a/includes/profiler/ProfileSection.php b/includes/profiler/ProfileSection.php
new file mode 100644
index 00000000..68ef6680
--- /dev/null
+++ b/includes/profiler/ProfileSection.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Function scope profiling assistant
+ *
+ * This 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
+ */
+
+/**
+ * Class for handling function-scope profiling
+ *
+ * @since 1.22
+ * @deprecated 1.25 No-op now
+ */
+class ProfileSection {
+ /**
+ * Begin profiling of a function and return an object that ends profiling
+ * of the function when that object leaves scope. As long as the object is
+ * not specifically linked to other objects, it will fall out of scope at
+ * the same moment that the function to be profiled terminates.
+ *
+ * This is typically called like:
+ * <code>$section = new ProfileSection( __METHOD__ );</code>
+ *
+ * @param string $name Name of the function to profile
+ */
+ public function __construct( $name ) {}
+}
diff --git a/includes/profiler/Profiler.php b/includes/profiler/Profiler.php
index 418b5d48..dbf80fa1 100644
--- a/includes/profiler/Profiler.php
+++ b/includes/profiler/Profiler.php
@@ -1,6 +1,6 @@
<?php
/**
- * Base class and functions for profiling.
+ * Base 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
@@ -23,113 +23,33 @@
*/
/**
- * 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
- */
-function wfProfileIn( $functionname ) {
- if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
- Profiler::instance();
- }
- if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
- Profiler::$__instance->profileIn( $functionname );
- }
-}
-
-/**
- * Stop profiling of a function
- * @param string $functionname Name of the function we have profiled
- */
-function wfProfileOut( $functionname = 'missing' ) {
- if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
- Profiler::instance();
- }
- if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
- Profiler::$__instance->profileOut( $functionname );
- }
-}
-
-/**
- * Class for handling function-scope profiling
- *
- * @since 1.22
- */
-class ProfileSection {
- protected $name; // string; method name
- protected $enabled = false; // boolean; whether profiling is enabled
-
- /**
- * Begin profiling of a function and return an object that ends profiling of
- * the function when that object leaves scope. As long as the object is not
- * specifically linked to other objects, it will fall out of scope at the same
- * moment that the function to be profiled terminates.
- *
- * This is typically called like:
- * <code>$section = new ProfileSection( __METHOD__ );</code>
- *
- * @param string $name Name of the function to profile
- */
- public function __construct( $name ) {
- $this->name = $name;
- if ( Profiler::$__instance === null ) { // use this directly to reduce overhead
- Profiler::instance();
- }
- if ( !( Profiler::$__instance instanceof ProfilerStub ) ) {
- $this->enabled = true;
- Profiler::$__instance->profileIn( $this->name );
- }
- }
-
- function __destruct() {
- if ( $this->enabled ) {
- Profiler::$__instance->profileOut( $this->name );
- }
- }
-}
-
-/**
- * Profiler base class that defines the interface and some trivial functionality
+ * Profiler base class that defines the interface and some trivial
+ * functionality
*
* @ingroup Profiler
*/
abstract class Profiler {
/** @var string|bool Profiler ID for bucketing data */
- protected $mProfileID = false;
+ protected $profileID = false;
/** @var bool Whether MediaWiki is in a SkinTemplate output context */
- protected $mTemplated = false;
-
+ protected $templated = false;
+ /** @var array All of the params passed from $wgProfiler */
+ protected $params = array();
+ /** @var IContextSource Current request context */
+ protected $context = null;
/** @var TransactionProfiler */
protected $trxProfiler;
-
- // @codingStandardsIgnoreStart PSR2.Classes.PropertyDeclaration.Underscore
- /** @var Profiler Do not call this outside Profiler and ProfileSection */
- public static $__instance = null;
- // @codingStandardsIgnoreEnd
+ /** @var Profiler */
+ private static $instance = null;
/**
* @param array $params
*/
public function __construct( array $params ) {
if ( isset( $params['profileID'] ) ) {
- $this->mProfileID = $params['profileID'];
+ $this->profileID = $params['profileID'];
}
+ $this->params = $params;
$this->trxProfiler = new TransactionProfiler();
}
@@ -138,332 +58,243 @@ abstract class Profiler {
* @return Profiler
*/
final public static function instance() {
- if ( self::$__instance === null ) {
- global $wgProfiler;
+ if ( self::$instance === null ) {
+ global $wgProfiler, $wgProfileLimit;
+
+ $params = array(
+ 'class' => 'ProfilerStub',
+ 'sampling' => 1,
+ 'threshold' => $wgProfileLimit,
+ 'output' => array(),
+ );
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'];
- }
- self::$__instance = new $class( $wgProfiler );
- } elseif ( $wgProfiler instanceof Profiler ) {
- self::$__instance = $wgProfiler; // back-compat
- } else {
- self::$__instance = new ProfilerStub( array() );
+ $params = array_merge( $params, $wgProfiler );
}
- }
- return self::$__instance;
- }
- /**
- * Set the profiler to a specific profiler instance. Mostly for dumpHTML
- * @param Profiler $p
- */
- final public static function setInstance( Profiler $p ) {
- self::$__instance = $p;
- }
+ $inSample = mt_rand( 0, $params['sampling'] - 1 ) === 0;
+ if ( PHP_SAPI === 'cli' || !$inSample ) {
+ $params['class'] = 'ProfilerStub';
+ }
- /**
- * Return whether this a stub profiler
- *
- * @return bool
- */
- abstract public function isStub();
+ if ( !is_array( $params['output'] ) ) {
+ $params['output'] = array( $params['output'] );
+ }
+
+ self::$instance = new $params['class']( $params );
+ }
+ return self::$instance;
+ }
/**
- * 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.
+ * Replace the current profiler with $profiler if no non-stub profiler is set
*
- * @see Profiler::logData()
- * @return bool
+ * @param Profiler $profiler
+ * @throws MWException
+ * @since 1.25
*/
- abstract public function isPersistent();
+ final public static function replaceStubInstance( Profiler $profiler ) {
+ if ( self::$instance && !( self::$instance instanceof ProfilerStub ) ) {
+ throw new MWException( 'Could not replace non-stub profiler instance.' );
+ } else {
+ self::$instance = $profiler;
+ }
+ }
/**
* @param string $id
*/
public function setProfileID( $id ) {
- $this->mProfileID = $id;
+ $this->profileID = $id;
}
/**
* @return string
*/
public function getProfileID() {
- if ( $this->mProfileID === false ) {
+ if ( $this->profileID === false ) {
return wfWikiID();
} else {
- return $this->mProfileID;
+ return $this->profileID;
}
}
/**
- * Called by wfProfieIn()
- *
- * @param string $functionname
- */
- abstract public function profileIn( $functionname );
-
- /**
- * Called by wfProfieOut()
- *
- * @param string $functionname
- */
- abstract public function profileOut( $functionname );
-
- /**
- * Mark a DB as in a transaction with one or more writes pending
+ * Sets the context for this Profiler
*
- * Note that there can be multiple connections to a single DB.
- *
- * @param string $server DB server
- * @param string $db DB name
- * @param string $id Resource ID string of connection
+ * @param IContextSource $context
+ * @since 1.25
*/
- public function transactionWritingIn( $server, $db, $id = '' ) {
- $this->trxProfiler->transactionWritingIn( $server, $db, $id );
+ public function setContext( $context ) {
+ $this->context = $context;
}
/**
- * 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.
+ * Gets the context for this Profiler
*
- * @param string $server DB server
- * @param string $db DB name
- * @param string $id Resource ID string of connection
+ * @return IContextSource
+ * @since 1.25
*/
- public function transactionWritingOut( $server, $db, $id = '' ) {
- $this->trxProfiler->transactionWritingOut( $server, $db, $id );
+ public function getContext() {
+ if ( $this->context ) {
+ return $this->context;
+ } else {
+ wfDebug( __METHOD__ . " called and \$context is null. " .
+ "Return RequestContext::getMain(); for sanity\n" );
+ return RequestContext::getMain();
+ }
}
- /**
- * Close opened profiling sections
- */
- abstract public function close();
+ // Kept BC for now, remove when possible
+ public function profileIn( $functionname ) {}
+ public function profileOut( $functionname ) {}
/**
- * Log the data to some store or even the page output
+ * Mark the start of a custom profiling frame (e.g. DB queries).
+ * The frame ends when the result of this method falls out of scope.
+ *
+ * @param string $section
+ * @return ScopedCallback|null
+ * @since 1.25
*/
- abstract public function logData();
+ abstract public function scopedProfileIn( $section );
/**
- * Mark this call as templated or not
- *
- * @param bool $t
+ * @param ScopedCallback $section
*/
- public function setTemplated( $t ) {
- $this->mTemplated = $t;
+ public function scopedProfileOut( ScopedCallback &$section = null ) {
+ $section = null;
}
/**
- * Returns a profiling output to be stored in debug file
- *
- * @return string
+ * @return TransactionProfiler
+ * @since 1.25
*/
- abstract public function getOutput();
+ public function getTransactionProfiler() {
+ return $this->trxProfiler;
+ }
/**
- * @return array
+ * Close opened profiling sections
*/
- abstract public function getRawData();
+ abstract public function close();
/**
- * Get the initial time of the request, based either on $wgRequestTime or
- * $wgRUstart. Will return null if not able to find data.
+ * Get all usable outputs.
*
- * @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
+ * @throws MWException
+ * @return array Array of ProfilerOutput instances.
+ * @since 1.25
*/
- protected function getTime( $metric = 'wall' ) {
- if ( $metric === 'cpu' || $metric === 'user' ) {
- $ru = wfGetRusage();
- if ( !$ru ) {
- return 0;
+ private function getOutputs() {
+ $outputs = array();
+ foreach ( $this->params['output'] as $outputType ) {
+ // The class may be specified as either the full class name (for
+ // example, 'ProfilerOutputUdp') or (for backward compatibility)
+ // the trailing portion of the class name (for example, 'udp').
+ $outputClass = strpos( $outputType, 'ProfilerOutput' ) === false
+ ? 'ProfilerOutput' . ucfirst( $outputType )
+ : $outputType;
+ if ( !class_exists( $outputClass ) ) {
+ throw new MWException( "'$outputType' is an invalid output type" );
}
- $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
- # it gives the total CPU time
- $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+ $outputInstance = new $outputClass( $this, $this->params );
+ if ( $outputInstance->canUse() ) {
+ $outputs[] = $outputInstance;
}
- return $time;
- } else {
- return microtime( true );
}
+ return $outputs;
}
/**
- * Get the initial time of the request, based either on $wgRequestTime or
- * $wgRUstart. Will return null if not able to find data.
+ * Log the data to some store or even the page output
*
- * @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
+ * @since 1.25
*/
- protected function getInitialTime( $metric = 'wall' ) {
- global $wgRequestTime, $wgRUstart;
+ public function logData() {
+ $request = $this->getContext()->getRequest();
- if ( $metric === 'cpu' || $metric === 'user' ) {
- if ( !count( $wgRUstart ) ) {
- return null;
- }
+ $timeElapsed = $request->getElapsedTime();
+ $timeElapsedThreshold = $this->params['threshold'];
+ if ( $timeElapsed <= $timeElapsedThreshold ) {
+ return;
+ }
- $time = $wgRUstart['ru_utime.tv_sec'] + $wgRUstart['ru_utime.tv_usec'] / 1e6;
- if ( $metric === 'cpu' ) {
- # This is the time of system calls, added to the user time
- # it gives the total CPU time
- $time += $wgRUstart['ru_stime.tv_sec'] + $wgRUstart['ru_stime.tv_usec'] / 1e6;
- }
- return $time;
- } else {
- if ( empty( $wgRequestTime ) ) {
- return null;
- } else {
- return $wgRequestTime;
- }
+ $outputs = $this->getOutputs();
+ if ( !$outputs ) {
+ return;
+ }
+
+ $stats = $this->getFunctionStats();
+ foreach ( $outputs as $output ) {
+ $output->log( $stats );
}
}
/**
- * Add an entry in the debug log file
- *
- * @param string $s String to output
+ * Get the content type sent out to the client.
+ * Used for profilers that output instead of store data.
+ * @return string
+ * @since 1.25
*/
- protected function debug( $s ) {
- if ( function_exists( 'wfDebug' ) ) {
- wfDebug( $s );
+ public function getContentType() {
+ foreach ( headers_list() as $header ) {
+ if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) {
+ return $m[1];
+ }
}
+ return null;
}
/**
- * Add an entry in the debug log group
+ * Mark this call as templated or not
*
- * @param string $group Group to send the message to
- * @param string $s String to output
+ * @param bool $t
*/
- protected function debugGroup( $group, $s ) {
- if ( function_exists( 'wfDebugLog' ) ) {
- wfDebugLog( $group, $s );
- }
+ public function setTemplated( $t ) {
+ $this->templated = $t;
}
-}
-
-/**
- * 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();
/**
- * Mark a DB as in a transaction with one or more writes pending
- *
- * Note that there can be multiple connections to a single DB.
+ * Was this call as templated or not
*
- * @param string $server DB server
- * @param string $db DB name
- * @param string $id ID string of transaction
+ * @return bool
*/
- 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();
-
- foreach ( $this->mDBTrxHoldingLocks as $name => &$info ) {
- $info['conns'][$name] = 1; // track all DBs in transactions for this transaction
- }
+ public function getTemplated() {
+ return $this->templated;
}
/**
- * Register the name and time of a method for slow DB trx detection
+ * Get the aggregated inclusive profiling data for each method
*
- * This method is only to be called by the Profiler class as methods finish
+ * The percent time for each time is based on the current "total" time
+ * used is based on all methods so far. This method can therefore be
+ * called several times in between several profiling calls without the
+ * delays in usage of the profiler skewing the results. A "-total" entry
+ * is always included in the results.
*
- * @param string $method Function name
- * @param float $realtime Wal time ellapsed
+ * When a call chain involves a method invoked within itself, any
+ * entries for the cyclic invocation should be be demarked with "@".
+ * This makes filtering them out easier and follows the xhprof style.
+ *
+ * @return array List of method entries arrays, each having:
+ * - name : method name
+ * - calls : the number of invoking calls
+ * - real : real time ellapsed (ms)
+ * - %real : percent real time
+ * - cpu : CPU time ellapsed (ms)
+ * - %cpu : percent CPU time
+ * - memory : memory used (bytes)
+ * - %memory : percent memory used
+ * - min_real : min real time in a call (ms)
+ * - max_real : max real time in a call (ms)
+ * @since 1.25
*/
- 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 );
- }
- }
- }
+ abstract public function getFunctionStats();
/**
- * 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.
+ * Returns a profiling output to be stored in debug file
*
- * @param string $server DB server
- * @param string $db DB name
- * @param string $id ID string of transaction
+ * @return string
*/
- 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 );
- }
- unset( $this->mDBTrxHoldingLocks[$name] );
- unset( $this->mDBTrxMethodTimes[$name] );
- }
+ abstract public function getOutput();
}
diff --git a/includes/profiler/ProfilerFunctions.php b/includes/profiler/ProfilerFunctions.php
new file mode 100644
index 00000000..4984e77d
--- /dev/null
+++ b/includes/profiler/ProfilerFunctions.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Core profiling functions. Have to exist before basically anything.
+ *
+ * This 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
+ */
+
+/**
+ * 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
+ * @deprecated 1.25
+ */
+function wfProfileIn( $functionname ) {
+}
+
+/**
+ * Stop profiling of a function
+ * @param string $functionname Name of the function we have profiled
+ * @deprecated 1.25
+ */
+function wfProfileOut( $functionname = 'missing' ) {
+}
diff --git a/includes/profiler/ProfilerMwprof.php b/includes/profiler/ProfilerMwprof.php
deleted file mode 100644
index af3c7741..00000000
--- a/includes/profiler/ProfilerMwprof.php
+++ /dev/null
@@ -1,256 +0,0 @@
-<?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/ProfilerSectionOnly.php b/includes/profiler/ProfilerSectionOnly.php
new file mode 100644
index 00000000..1f8d33b1
--- /dev/null
+++ b/includes/profiler/ProfilerSectionOnly.php
@@ -0,0 +1,104 @@
+<?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
+ */
+
+/**
+ * Profiler that only tracks explicit profiling sections
+ *
+ * @code
+ * $wgProfiler['class'] = 'ProfilerSectionOnly';
+ * $wgProfiler['output'] = 'text';
+ * $wgProfiler['visible'] = true;
+ * @endcode
+ *
+ * @author Aaron Schulz
+ * @ingroup Profiler
+ * @since 1.25
+ */
+class ProfilerSectionOnly extends Profiler {
+ /** @var SectionProfiler */
+ protected $sprofiler;
+
+ public function __construct( array $params = array() ) {
+ parent::__construct( $params );
+ $this->sprofiler = new SectionProfiler();
+ }
+
+ public function scopedProfileIn( $section ) {
+ return $this->sprofiler->scopedProfileIn( $section );
+ }
+
+ public function close() {
+ }
+
+ public function getFunctionStats() {
+ return $this->sprofiler->getFunctionStats();
+ }
+
+ public function getOutput() {
+ return $this->getFunctionReport();
+ }
+
+ /**
+ * Get a report of profiled functions sorted by inclusive wall clock time
+ * in descending order.
+ *
+ * Each line of the report includes this data:
+ * - Function name
+ * - Number of times function was called
+ * - Total wall clock time spent in function in microseconds
+ * - Minimum wall clock time spent in function in microseconds
+ * - Average wall clock time spent in function in microseconds
+ * - Maximum wall clock time spent in function in microseconds
+ * - Percentage of total wall clock time spent in function
+ * - Total delta of memory usage from start to end of function in bytes
+ *
+ * @return string
+ */
+ protected function getFunctionReport() {
+ $data = $this->getFunctionStats();
+ usort( $data, function( $a, $b ) {
+ if ( $a['real'] === $b['real'] ) {
+ return 0;
+ }
+ return ( $a['real'] > $b['real'] ) ? -1 : 1; // descending
+ } );
+
+ $width = 140;
+ $nameWidth = $width - 65;
+ $format = "%-{$nameWidth}s %6d %9d %9d %9d %9d %7.3f%% %9d";
+ $out = array();
+ $out[] = sprintf( "%-{$nameWidth}s %6s %9s %9s %9s %9s %7s %9s",
+ 'Name', 'Calls', 'Total', 'Min', 'Each', 'Max', '%', 'Mem'
+ );
+ foreach ( $data as $stats ) {
+ $out[] = sprintf( $format,
+ $stats['name'],
+ $stats['calls'],
+ $stats['real'] * 1000,
+ $stats['min_real'] * 1000,
+ $stats['real'] / $stats['calls'] * 1000,
+ $stats['max_real'] * 1000,
+ $stats['%real'],
+ $stats['memory']
+ );
+ }
+ return implode( "\n", $out );
+ }
+}
diff --git a/includes/profiler/ProfilerSimpleText.php b/includes/profiler/ProfilerSimpleText.php
deleted file mode 100644
index 0ee7aad2..00000000
--- a/includes/profiler/ProfilerSimpleText.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-/**
- * Profiler showing output in page source.
- *
- * This 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
- */
-
-/**
- * The least sophisticated profiler output class possible, view your source! :)
- *
- * Put the following 2 lines in StartProfiler.php:
- *
- * $wgProfiler['class'] = 'ProfilerSimpleText';
- * $wgProfiler['visible'] = true;
- *
- * @ingroup Profiler
- */
-class ProfilerSimpleText extends ProfilerStandard {
- public $visible = false; /* Show as <PRE> or <!-- ? */
- static private $out;
-
- public function __construct( $profileConfig ) {
- if ( isset( $profileConfig['visible'] ) && $profileConfig['visible'] ) {
- $this->visible = true;
- }
- parent::__construct( $profileConfig );
- }
-
- protected function collateOnly() {
- return true;
- }
-
- public function logData() {
- if ( $this->mTemplated ) {
- $this->close();
- $totalReal = isset( $this->mCollated['-total'] )
- ? $this->mCollated['-total']['real']
- : 0; // profiling mismatch error?
- uasort( $this->mCollated, array( 'self', 'sort' ) );
- array_walk( $this->mCollated, array( 'self', 'format' ), $totalReal );
- if ( PHP_SAPI === 'cli' ) {
- print "<!--\n" . self::$out . "\n-->\n";
- } elseif ( $this->getContentType() === 'text/html' ) {
- if ( $this->visible ) {
- print '<pre>' . self::$out . '</pre>';
- } else {
- print "<!--\n" . self::$out . "\n-->\n";
- }
- } elseif ( $this->getContentType() === 'text/javascript' ) {
- print "\n/*\n" . self::$out . "*/\n";
- } elseif ( $this->getContentType() === 'text/css' ) {
- print "\n/*\n" . self::$out . "*/\n";
- }
- }
- }
-
- static function sort( $a, $b ) {
- return $a['real'] < $b['real']; /* sort descending by time elapsed */
- }
-
- static function format( $item, $key, $totalReal ) {
- $perc = $totalReal ? $item['real'] / $totalReal * 100 : 0;
- self::$out .= sprintf( "%6.2f%% %3.6f %6d - %s\n",
- $perc, $item['real'], $item['count'], $key );
- }
-}
diff --git a/includes/profiler/ProfilerSimpleTrace.php b/includes/profiler/ProfilerSimpleTrace.php
deleted file mode 100644
index 2a444948..00000000
--- a/includes/profiler/ProfilerSimpleTrace.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-/**
- * Profiler showing execution trace.
- *
- * This 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
- */
-
-/**
- * Execution trace profiler
- * @todo document methods (?)
- * @ingroup Profiler
- */
-class ProfilerSimpleTrace extends ProfilerStandard {
- protected $trace = "Beginning trace: \n";
- protected $memory = 0;
-
- 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";
- }
-
- public function profileOut( $functionname ) {
- $item = end( $this->mWorkStack );
-
- parent::profileOut( $functionname );
-
- 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 ) {
- $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";
- }
- }
-
- protected function memoryDiff() {
- $diff = memory_get_usage() - $this->memory;
- $this->memory = memory_get_usage();
- return $diff / 1024;
- }
-
- 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
deleted file mode 100644
index 627b4de2..00000000
--- a/includes/profiler/ProfilerSimpleUDP.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-/**
- * Profiler sending messages over UDP.
- *
- * This 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
- */
-
-/**
- * ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon
- * (the one from
- * http://git.wikimedia.org/tree/operations%2Fsoftware.git/master/udpprofile)
- * @ingroup Profiler
- */
-class ProfilerSimpleUDP extends ProfilerStandard {
- protected function collateOnly() {
- return true;
- }
-
- public function isPersistent() {
- return true;
- }
-
- public function logData() {
- global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgUDPProfilerFormatString;
-
- $this->close();
-
- if ( !function_exists( 'socket_create' ) ) {
- # Sockets are not enabled
- return;
- }
-
- $sock = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- $plength = 0;
- $packet = "";
- foreach ( $this->mCollated as $entry => $pfdata ) {
- if ( !isset( $pfdata['count'] )
- || !isset( $pfdata['cpu'] )
- || !isset( $pfdata['cpu_sq'] )
- || !isset( $pfdata['real'] )
- || !isset( $pfdata['real_sq'] ) ) {
- continue;
- }
- $pfline = sprintf( $wgUDPProfilerFormatString, $this->getProfileID(), $pfdata['count'],
- $pfdata['cpu'], $pfdata['cpu_sq'], $pfdata['real'], $pfdata['real_sq'], $entry,
- $pfdata['memory'] );
- $length = strlen( $pfline );
- /* printf("<!-- $pfline -->"); */
- if ( $length + $plength > 1400 ) {
- socket_sendto( $sock, $packet, $plength, 0, $wgUDPProfilerHost, $wgUDPProfilerPort );
- $packet = "";
- $plength = 0;
- }
- $packet .= $pfline;
- $plength += $length;
- }
- socket_sendto( $sock, $packet, $plength, 0x100, $wgUDPProfilerHost, $wgUDPProfilerPort );
- }
-}
diff --git a/includes/profiler/ProfilerStandard.php b/includes/profiler/ProfilerStandard.php
deleted file mode 100644
index cc134165..00000000
--- a/includes/profiler/ProfilerStandard.php
+++ /dev/null
@@ -1,559 +0,0 @@
-<?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 1d3b65d2..244b4e4b 100644
--- a/includes/profiler/ProfilerStub.php
+++ b/includes/profiler/ProfilerStub.php
@@ -27,18 +27,11 @@
* @ingroup Profiler
*/
class ProfilerStub extends Profiler {
- public function isStub() {
- return true;
+ public function scopedProfileIn( $section ) {
+ return null; // no-op
}
- public function isPersistent() {
- return false;
- }
-
- public function profileIn( $fn ) {
- }
-
- public function profileOut( $fn ) {
+ public function getFunctionStats() {
}
public function getOutput() {
@@ -47,20 +40,10 @@ class ProfilerStub extends Profiler {
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();
+ public function logData() {
}
}
diff --git a/includes/profiler/ProfilerXhprof.php b/includes/profiler/ProfilerXhprof.php
new file mode 100644
index 00000000..f36cdc1a
--- /dev/null
+++ b/includes/profiler/ProfilerXhprof.php
@@ -0,0 +1,194 @@
+<?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
+ */
+
+/**
+ * Profiler wrapper for XHProf extension.
+ *
+ * @code
+ * $wgProfiler['class'] = 'ProfilerXhprof';
+ * $wgProfiler['flags'] = XHPROF_FLAGS_NO_BUILTINS;
+ * $wgProfiler['output'] = 'text';
+ * $wgProfiler['visible'] = true;
+ * @endcode
+ *
+ * @code
+ * $wgProfiler['class'] = 'ProfilerXhprof';
+ * $wgProfiler['flags'] = XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_NO_BUILTINS;
+ * $wgProfiler['output'] = 'udp';
+ * @endcode
+ *
+ * ProfilerXhprof profiles all functions using the XHProf PHP extenstion.
+ * For PHP5 users, this extension can be installed via PECL or your operating
+ * system's package manager. XHProf support is built into HHVM.
+ *
+ * To restrict the functions for which profiling data is collected, you can
+ * use either a whitelist ($wgProfiler['include']) or a blacklist
+ * ($wgProfiler['exclude']) containing an array of function names. The
+ * blacklist functionality is built into HHVM and will completely exclude the
+ * named functions from profiling collection. The whitelist is implemented by
+ * Xhprof class which will filter the data collected by XHProf before reporting.
+ * See documentation for the Xhprof class and the XHProf extension for
+ * additional information.
+ *
+ * @author Bryan Davis <bd808@wikimedia.org>
+ * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
+ * @ingroup Profiler
+ * @see Xhprof
+ * @see https://php.net/xhprof
+ * @see https://github.com/facebook/hhvm/blob/master/hphp/doc/profiling.md
+ */
+class ProfilerXhprof extends Profiler {
+ /**
+ * @var Xhprof $xhprof
+ */
+ protected $xhprof;
+
+ /**
+ * Profiler for explicit, arbitrary, frame labels
+ * @var SectionProfiler
+ */
+ protected $sprofiler;
+
+ /**
+ * @param array $params
+ * @see Xhprof::__construct()
+ */
+ public function __construct( array $params = array() ) {
+ parent::__construct( $params );
+ $this->xhprof = new Xhprof( $params );
+ $this->sprofiler = new SectionProfiler();
+ }
+
+ public function scopedProfileIn( $section ) {
+ return $this->sprofiler->scopedProfileIn( $section );
+ }
+
+ /**
+ * No-op for xhprof profiling.
+ */
+ public function close() {
+ }
+
+ public function getFunctionStats() {
+ $metrics = $this->xhprof->getCompleteMetrics();
+ $profile = array();
+
+ $main = null; // units in ms
+ foreach ( $metrics as $fname => $stats ) {
+ // Convert elapsed times from μs to ms to match interface
+ $entry = array(
+ 'name' => $fname,
+ 'calls' => $stats['ct'],
+ 'real' => $stats['wt']['total'] / 1000,
+ '%real' => $stats['wt']['percent'],
+ 'cpu' => isset( $stats['cpu'] ) ? $stats['cpu']['total'] / 1000 : 0,
+ '%cpu' => isset( $stats['cpu'] ) ? $stats['cpu']['percent'] : 0,
+ 'memory' => isset( $stats['mu'] ) ? $stats['mu']['total'] : 0,
+ '%memory' => isset( $stats['mu'] ) ? $stats['mu']['percent'] : 0,
+ 'min_real' => $stats['wt']['min'] / 1000,
+ 'max_real' => $stats['wt']['max'] / 1000
+ );
+ $profile[] = $entry;
+ if ( $fname === 'main()' ) {
+ $main = $entry;
+ }
+ }
+
+ // Merge in all of the custom profile sections
+ foreach ( $this->sprofiler->getFunctionStats() as $stats ) {
+ if ( $stats['name'] === '-total' ) {
+ // Discard section profiler running totals
+ continue;
+ }
+
+ // @note: getFunctionStats() values already in ms
+ $stats['%real'] = $main['real'] ? $stats['real'] / $main['real'] * 100 : 0;
+ $stats['%cpu'] = $main['cpu'] ? $stats['cpu'] / $main['cpu'] * 100 : 0;
+ $stats['%memory'] = $main['memory'] ? $stats['memory'] / $main['memory'] * 100 : 0;
+ $profile[] = $stats; // assume no section names collide with $metrics
+ }
+
+ return $profile;
+ }
+
+ /**
+ * Returns a profiling output to be stored in debug file
+ *
+ * @return string
+ */
+ public function getOutput() {
+ return $this->getFunctionReport();
+ }
+
+ /**
+ * Get a report of profiled functions sorted by inclusive wall clock time
+ * in descending order.
+ *
+ * Each line of the report includes this data:
+ * - Function name
+ * - Number of times function was called
+ * - Total wall clock time spent in function in microseconds
+ * - Minimum wall clock time spent in function in microseconds
+ * - Average wall clock time spent in function in microseconds
+ * - Maximum wall clock time spent in function in microseconds
+ * - Percentage of total wall clock time spent in function
+ * - Total delta of memory usage from start to end of function in bytes
+ *
+ * @return string
+ */
+ protected function getFunctionReport() {
+ $data = $this->getFunctionStats();
+ usort( $data, function( $a, $b ) {
+ if ( $a['real'] === $b['real'] ) {
+ return 0;
+ }
+ return ( $a['real'] > $b['real'] ) ? -1 : 1; // descending
+ } );
+
+ $width = 140;
+ $nameWidth = $width - 65;
+ $format = "%-{$nameWidth}s %6d %9d %9d %9d %9d %7.3f%% %9d";
+ $out = array();
+ $out[] = sprintf( "%-{$nameWidth}s %6s %9s %9s %9s %9s %7s %9s",
+ 'Name', 'Calls', 'Total', 'Min', 'Each', 'Max', '%', 'Mem'
+ );
+ foreach ( $data as $stats ) {
+ $out[] = sprintf( $format,
+ $stats['name'],
+ $stats['calls'],
+ $stats['real'] * 1000,
+ $stats['min_real'] * 1000,
+ $stats['real'] / $stats['calls'] * 1000,
+ $stats['max_real'] * 1000,
+ $stats['%real'],
+ $stats['memory']
+ );
+ }
+ return implode( "\n", $out );
+ }
+
+ /**
+ * Retrieve raw data from xhprof
+ * @return array
+ */
+ public function getRawData() {
+ return $this->xhprof->getRawData();
+ }
+}
diff --git a/includes/profiler/SectionProfiler.php b/includes/profiler/SectionProfiler.php
new file mode 100644
index 00000000..245022df
--- /dev/null
+++ b/includes/profiler/SectionProfiler.php
@@ -0,0 +1,530 @@
+<?php
+/**
+ * Arbitrary section name based PHP 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
+ * @author Aaron Schulz
+ */
+
+/**
+ * Custom PHP profiler for parser/DB type section names that xhprof/xdebug can't handle
+ *
+ * @since 1.25
+ */
+class SectionProfiler {
+ /** @var array Map of (mem,real,cpu) */
+ protected $start;
+ /** @var array Map of (mem,real,cpu) */
+ protected $end;
+ /** @var array List of resolved profile calls with start/end data */
+ protected $stack = array();
+ /** @var array Queue of open profile calls with start data */
+ protected $workStack = array();
+
+ /** @var array Map of (function name => aggregate data array) */
+ protected $collated = array();
+ /** @var bool */
+ protected $collateDone = false;
+
+ /** @var bool Whether to collect the full stack trace or just aggregates */
+ protected $collateOnly = true;
+ /** @var array Cache of a standard broken collation entry */
+ protected $errorEntry;
+ /** @var callable Cache of a profile out callback */
+ protected $profileOutCallback;
+
+ /**
+ * @param array $params
+ */
+ public function __construct( array $params = array() ) {
+ $this->errorEntry = $this->getErrorEntry();
+ $this->collateOnly = empty( $params['trace'] );
+ $this->profileOutCallback = function ( $profiler, $section ) {
+ $profiler->profileOutInternal( $section );
+ };
+ }
+
+ /**
+ * @param string $section
+ * @return ScopedCallback
+ */
+ public function scopedProfileIn( $section ) {
+ $this->profileInInternal( $section );
+
+ return new SectionProfileCallback( $this, $section );
+ }
+
+ /**
+ * @param ScopedCallback $section
+ */
+ public function scopedProfileOut( ScopedCallback &$section ) {
+ $section = null;
+ }
+
+ /**
+ * Get the aggregated inclusive profiling data for each method
+ *
+ * The percent time for each time is based on the current "total" time
+ * used is based on all methods so far. This method can therefore be
+ * called several times in between several profiling calls without the
+ * delays in usage of the profiler skewing the results. A "-total" entry
+ * is always included in the results.
+ *
+ * @return array List of method entries arrays, each having:
+ * - name : method name
+ * - calls : the number of invoking calls
+ * - real : real time ellapsed (ms)
+ * - %real : percent real time
+ * - cpu : real time ellapsed (ms)
+ * - %cpu : percent real time
+ * - memory : memory used (bytes)
+ * - %memory : percent memory used
+ * - min_real : min real time in a call (ms)
+ * - max_real : max real time in a call (ms)
+ */
+ public function getFunctionStats() {
+ $this->collateData();
+
+ $totalCpu = max( $this->end['cpu'] - $this->start['cpu'], 0 );
+ $totalReal = max( $this->end['real'] - $this->start['real'], 0 );
+ $totalMem = max( $this->end['memory'] - $this->start['memory'], 0 );
+
+ $profile = array();
+ foreach ( $this->collated as $fname => $data ) {
+ $profile[] = array(
+ 'name' => $fname,
+ 'calls' => $data['count'],
+ 'real' => $data['real'] * 1000,
+ '%real' => $totalReal ? 100 * $data['real'] / $totalReal : 0,
+ 'cpu' => $data['cpu'] * 1000,
+ '%cpu' => $totalCpu ? 100 * $data['cpu'] / $totalCpu : 0,
+ 'memory' => $data['memory'],
+ '%memory' => $totalMem ? 100 * $data['memory'] / $totalMem : 0,
+ 'min_real' => 1000 * $data['min_real'],
+ 'max_real' => 1000 * $data['max_real']
+ );
+ }
+
+ $profile[] = array(
+ 'name' => '-total',
+ 'calls' => 1,
+ 'real' => 1000 * $totalReal,
+ '%real' => 100,
+ 'cpu' => 1000 * $totalCpu,
+ '%cpu' => 100,
+ 'memory' => $totalMem,
+ '%memory' => 100,
+ 'min_real' => 1000 * $totalReal,
+ 'max_real' => 1000 * $totalReal
+ );
+
+ return $profile;
+ }
+
+ /**
+ * Clear all of the profiling data for another run
+ */
+ public function reset() {
+ $this->start = null;
+ $this->end = null;
+ $this->stack = array();
+ $this->workStack = array();
+ $this->collated = array();
+ $this->collateDone = false;
+ }
+
+ /**
+ * @return array Initial collation entry
+ */
+ protected function getZeroEntry() {
+ return array(
+ 'cpu' => 0.0,
+ 'real' => 0.0,
+ 'memory' => 0,
+ 'count' => 0,
+ 'min_real' => 0.0,
+ 'max_real' => 0.0
+ );
+ }
+
+ /**
+ * @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
+ */
+ protected function updateEntry( $name, $elapsedCpu, $elapsedReal, $memChange ) {
+ $entry =& $this->collated[$name];
+ if ( !is_array( $entry ) ) {
+ $entry = $this->getZeroEntry();
+ $this->collated[$name] =& $entry;
+ }
+ $entry['cpu'] += $elapsedCpu;
+ $entry['real'] += $elapsedReal;
+ $entry['memory'] += $memChange > 0 ? $memChange : 0;
+ $entry['count']++;
+ $entry['min_real'] = min( $entry['min_real'], $elapsedReal );
+ $entry['max_real'] = max( $entry['max_real'], $elapsedReal );
+ }
+
+ /**
+ * This method should not be called outside SectionProfiler
+ *
+ * @param string $functionname
+ */
+ public function profileInInternal( $functionname ) {
+ // Once the data is collated for reports, any future calls
+ // should clear the collation cache so the next report will
+ // reflect them. This matters when trace mode is used.
+ $this->collateDone = false;
+
+ $cpu = $this->getTime( 'cpu' );
+ $real = $this->getTime( 'wall' );
+ $memory = memory_get_usage();
+
+ if ( $this->start === null ) {
+ $this->start = array( 'cpu' => $cpu, 'real' => $real, 'memory' => $memory );
+ }
+
+ $this->workStack[] = array(
+ $functionname,
+ count( $this->workStack ),
+ $real,
+ $cpu,
+ $memory
+ );
+ }
+
+ /**
+ * This method should not be called outside SectionProfiler
+ *
+ * @param string $functionname
+ */
+ public function profileOutInternal( $functionname ) {
+ $item = array_pop( $this->workStack );
+ if ( $item === null ) {
+ $this->debugGroup( 'profileerror', "Profiling error: $functionname" );
+ return;
+ }
+ list( $ofname, /* $ocount */, $ortime, $octime, $omem ) = $item;
+
+ if ( $functionname === 'close' ) {
+ $message = "Profile section ended by close(): {$ofname}";
+ $this->debugGroup( 'profileerror', $message );
+ if ( $this->collateOnly ) {
+ $this->collated[$message] = $this->errorEntry;
+ } else {
+ $this->stack[] = 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->collateOnly ) {
+ $this->collated[$message] = $this->errorEntry;
+ } else {
+ $this->stack[] = array( $message, 0, 0.0, 0.0, 0, 0.0, 0.0, 0 );
+ }
+ }
+
+ $realTime = $this->getTime( 'wall' );
+ $cpuTime = $this->getTime( 'cpu' );
+ $memUsage = memory_get_usage();
+
+ if ( $this->collateOnly ) {
+ $elapsedcpu = $cpuTime - $octime;
+ $elapsedreal = $realTime - $ortime;
+ $memchange = $memUsage - $omem;
+ $this->updateEntry( $functionname, $elapsedcpu, $elapsedreal, $memchange );
+ } else {
+ $this->stack[] = array_merge( $item, array( $realTime, $cpuTime, $memUsage ) );
+ }
+
+ $this->end = array(
+ 'cpu' => $cpuTime,
+ 'real' => $realTime,
+ 'memory' => $memUsage
+ );
+ }
+
+ /**
+ * Returns a tree of function calls with their real times
+ * @return string
+ * @throws Exception
+ */
+ public function getCallTreeReport() {
+ if ( $this->collateOnly ) {
+ throw new Exception( "Tree is only available for trace profiling." );
+ }
+ return implode( '', array_map(
+ array( $this, 'getCallTreeLine' ), $this->remapCallTree( $this->stack )
+ ) );
+ }
+
+ /**
+ * 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 ) {
+ // $entry has (name, level, stime, scpu, smem, etime, ecpu, emem)
+ 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 collated data
+ */
+ protected function collateData() {
+ if ( $this->collateDone ) {
+ return;
+ }
+ $this->collateDone = true;
+ // Close opened profiling sections
+ while ( count( $this->workStack ) ) {
+ $this->profileOutInternal( 'close' );
+ }
+
+ if ( $this->collateOnly ) {
+ return; // already collated as methods exited
+ }
+
+ $this->collated = array();
+
+ # Estimate profiling overhead
+ $oldEnd = $this->end;
+ $profileCount = count( $this->stack );
+ $this->calculateOverhead( $profileCount );
+
+ # First, subtract the overhead!
+ $overheadTotal = $overheadMemory = $overheadInternal = array();
+ foreach ( $this->stack 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->stack 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->stack, $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 );
+ }
+ }
+
+ $this->updateEntry( $fname, $elapsedCpu, $elapsedReal, $memchange );
+ }
+
+ $this->collated['-overhead-total']['count'] = $profileCount;
+ arsort( $this->collated, SORT_NUMERIC );
+
+ // Unclobber the end info map (the overhead checking alters it)
+ $this->end = $oldEnd;
+ }
+
+ /**
+ * Dummy calls to calculate profiling overhead
+ *
+ * @param int $profileCount
+ */
+ protected function calculateOverhead( $profileCount ) {
+ $this->profileInInternal( '-overhead-total' );
+ for ( $i = 0; $i < $profileCount; $i++ ) {
+ $this->profileInInternal( '-overhead-internal' );
+ $this->profileOutInternal( '-overhead-internal' );
+ }
+ $this->profileOutInternal( '-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 initial time of the request, based either on $wgRequestTime or
+ * $wgRUstart. Will return null if not able to find data.
+ *
+ * @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 getTime( $metric = 'wall' ) {
+ if ( $metric === 'cpu' || $metric === 'user' ) {
+ $ru = wfGetRusage();
+ if ( !$ru ) {
+ return 0;
+ }
+ $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
+ # it gives the total CPU time
+ $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+ }
+ return $time;
+ } else {
+ return microtime( true );
+ }
+ }
+
+ /**
+ * Add an entry in the debug log file
+ *
+ * @param string $s String to output
+ */
+ protected function debug( $s ) {
+ if ( function_exists( 'wfDebug' ) ) {
+ wfDebug( $s );
+ }
+ }
+
+ /**
+ * Add an entry in the debug log group
+ *
+ * @param string $group Group to send the message to
+ * @param string $s String to output
+ */
+ protected function debugGroup( $group, $s ) {
+ if ( function_exists( 'wfDebugLog' ) ) {
+ wfDebugLog( $group, $s );
+ }
+ }
+}
+
+/**
+ * Subclass ScopedCallback to avoid call_user_func_array(), which is slow
+ *
+ * This class should not be used outside of SectionProfiler
+ */
+class SectionProfileCallback extends ScopedCallback {
+ /** @var SectionProfiler */
+ protected $profiler;
+ /** @var string */
+ protected $section;
+
+ /**
+ * @param SectionProfiler $profiler
+ * @param string $section
+ */
+ public function __construct( SectionProfiler $profiler, $section ) {
+ parent::__construct( null );
+ $this->profiler = $profiler;
+ $this->section = $section;
+ }
+
+ function __destruct() {
+ $this->profiler->profileOutInternal( $this->section );
+ }
+}
diff --git a/includes/profiler/TransactionProfiler.php b/includes/profiler/TransactionProfiler.php
new file mode 100644
index 00000000..f02d66f8
--- /dev/null
+++ b/includes/profiler/TransactionProfiler.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Transaction profiling for contention
+ *
+ * This 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
+ * @author Aaron Schulz
+ */
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\NullLogger;
+/**
+ * Helper class that detects high-contention DB queries via profiling calls
+ *
+ * This class is meant to work with a DatabaseBase object, which manages queries
+ *
+ * @since 1.24
+ */
+class TransactionProfiler implements LoggerAwareInterface {
+ /** @var float Seconds */
+ protected $dbLockThreshold = 3.0;
+ /** @var float Seconds */
+ protected $eventThreshold = .25;
+
+ /** @var array transaction ID => (write start time, list of DBs involved) */
+ protected $dbTrxHoldingLocks = array();
+ /** @var array transaction ID => list of (query name, start time, end time) */
+ protected $dbTrxMethodTimes = array();
+
+ /** @var array */
+ protected $hits = array(
+ 'writes' => 0,
+ 'queries' => 0,
+ 'conns' => 0,
+ 'masterConns' => 0
+ );
+ /** @var array */
+ protected $expect = array(
+ 'writes' => INF,
+ 'queries' => INF,
+ 'conns' => INF,
+ 'masterConns' => INF,
+ 'maxAffected' => INF
+ );
+ /** @var array */
+ protected $expectBy = array();
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct() {
+ $this->setLogger( new NullLogger() );
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Set performance expectations
+ *
+ * With conflicting expect, the most specific ones will be used
+ *
+ * @param string $event (writes,queries,conns,mConns)
+ * @param integer $value Maximum count of the event
+ * @param string $fname Caller
+ * @since 1.25
+ */
+ public function setExpectation( $event, $value, $fname ) {
+ $this->expect[$event] = isset( $this->expect[$event] )
+ ? min( $this->expect[$event], $value )
+ : $value;
+ if ( $this->expect[$event] == $value ) {
+ $this->expectBy[$event] = $fname;
+ }
+ }
+
+ /**
+ * Reset performance expectations and hit counters
+ *
+ * @since 1.25
+ */
+ public function resetExpectations() {
+ foreach ( $this->hits as &$val ) {
+ $val = 0;
+ }
+ unset( $val );
+ foreach ( $this->expect as &$val ) {
+ $val = INF;
+ }
+ unset( $val );
+ $this->expectBy = array();
+ }
+
+ /**
+ * Mark a DB as having been connected to with a new handle
+ *
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ * @param bool $isMaster
+ */
+ public function recordConnection( $server, $db, $isMaster ) {
+ // Report when too many connections happen...
+ if ( $this->hits['conns']++ == $this->expect['conns'] ) {
+ $this->reportExpectationViolated( 'conns', "[connect to $server ($db)]" );
+ }
+ if ( $isMaster && $this->hits['masterConns']++ == $this->expect['masterConns'] ) {
+ $this->reportExpectationViolated( 'masterConns', "[connect to $server ($db)]" );
+ }
+ }
+
+ /**
+ * 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 transactionWritingIn( $server, $db, $id ) {
+ $name = "{$server} ({$db}) (TRX#$id)";
+ if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
+ $this->logger->info( "Nested transaction for '$name' - out of sync." );
+ }
+ $this->dbTrxHoldingLocks[$name] = array(
+ 'start' => microtime( true ),
+ 'conns' => array(), // all connections involved
+ );
+ $this->dbTrxMethodTimes[$name] = array();
+
+ foreach ( $this->dbTrxHoldingLocks as $name => &$info ) {
+ // Track all DBs in transactions for this transaction
+ $info['conns'][$name] = 1;
+ }
+ }
+
+ /**
+ * Register the name and time of a method for slow DB trx detection
+ *
+ * This assumes that all queries are synchronous (non-overlapping)
+ *
+ * @param string $query Function name or generalized SQL
+ * @param float $sTime Starting UNIX wall time
+ * @param bool $isWrite Whether this is a write query
+ * @param integer $n Number of affected rows
+ */
+ public function recordQueryCompletion( $query, $sTime, $isWrite = false, $n = 0 ) {
+ $eTime = microtime( true );
+ $elapsed = ( $eTime - $sTime );
+
+ if ( $isWrite && $n > $this->expect['maxAffected'] ) {
+ $this->logger->info( "Query affected $n row(s):\n" . $query . "\n" . wfBacktrace( true ) );
+ }
+
+ // Report when too many writes/queries happen...
+ if ( $this->hits['queries']++ == $this->expect['queries'] ) {
+ $this->reportExpectationViolated( 'queries', $query );
+ }
+ if ( $isWrite && $this->hits['writes']++ == $this->expect['writes'] ) {
+ $this->reportExpectationViolated( 'writes', $query );
+ }
+
+ if ( !$this->dbTrxHoldingLocks ) {
+ // Short-circuit
+ return;
+ } elseif ( !$isWrite && $elapsed < $this->eventThreshold ) {
+ // Not an important query nor slow enough
+ return;
+ }
+
+ foreach ( $this->dbTrxHoldingLocks as $name => $info ) {
+ $lastQuery = end( $this->dbTrxMethodTimes[$name] );
+ if ( $lastQuery ) {
+ // Additional query in the trx...
+ $lastEnd = $lastQuery[2];
+ if ( $sTime >= $lastEnd ) { // sanity check
+ if ( ( $sTime - $lastEnd ) > $this->eventThreshold ) {
+ // Add an entry representing the time spent doing non-queries
+ $this->dbTrxMethodTimes[$name][] = array( '...delay...', $lastEnd, $sTime );
+ }
+ $this->dbTrxMethodTimes[$name][] = array( $query, $sTime, $eTime );
+ }
+ } else {
+ // First query in the trx...
+ if ( $sTime >= $info['start'] ) { // sanity check
+ $this->dbTrxMethodTimes[$name][] = array( $query, $sTime, $eTime );
+ }
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ public function transactionWritingOut( $server, $db, $id ) {
+ $name = "{$server} ({$db}) (TRX#$id)";
+ if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
+ $this->logger->info( "Detected no transaction for '$name' - out of sync." );
+ return;
+ }
+ // Fill in the last non-query period...
+ $lastQuery = end( $this->dbTrxMethodTimes[$name] );
+ if ( $lastQuery ) {
+ $now = microtime( true );
+ $lastEnd = $lastQuery[2];
+ if ( ( $now - $lastEnd ) > $this->eventThreshold ) {
+ $this->dbTrxMethodTimes[$name][] = array( '...delay...', $lastEnd, $now );
+ }
+ }
+ // Check for any slow queries or non-query periods...
+ $slow = false;
+ foreach ( $this->dbTrxMethodTimes[$name] as $info ) {
+ $elapsed = ( $info[2] - $info[1] );
+ if ( $elapsed >= $this->dbLockThreshold ) {
+ $slow = true;
+ break;
+ }
+ }
+ if ( $slow ) {
+ $dbs = implode( ', ', array_keys( $this->dbTrxHoldingLocks[$name]['conns'] ) );
+ $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
+ foreach ( $this->dbTrxMethodTimes[$name] as $i => $info ) {
+ list( $query, $sTime, $end ) = $info;
+ $msg .= sprintf( "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), $query );
+ }
+ $this->logger->info( $msg );
+ }
+ unset( $this->dbTrxHoldingLocks[$name] );
+ unset( $this->dbTrxMethodTimes[$name] );
+ }
+
+ /**
+ * @param string $expect
+ * @param string $query
+ */
+ protected function reportExpectationViolated( $expect, $query ) {
+ global $wgRequest;
+
+ $n = $this->expect[$expect];
+ $by = $this->expectBy[$expect];
+ $this->logger->info(
+ "[{$wgRequest->getMethod()}] Expectation ($expect <= $n) by $by not met:\n$query\n" . wfBacktrace( true )
+ );
+ }
+}
diff --git a/includes/profiler/output/ProfilerOutput.php b/includes/profiler/output/ProfilerOutput.php
new file mode 100644
index 00000000..3473e0b2
--- /dev/null
+++ b/includes/profiler/output/ProfilerOutput.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Profiler
+ */
+
+/**
+ * Base class for profiling output
+ *
+ * Since 1.25
+ */
+abstract class ProfilerOutput {
+ /** @var Profiler */
+ protected $collector;
+ /** @var array Configuration of $wgProfiler */
+ protected $params = array();
+
+ /**
+ * Constructor
+ * @param Profiler $collector The actual profiler
+ * @param array $params Configuration array, passed down from $wgProfiler
+ */
+ public function __construct( Profiler $collector, array $params ) {
+ $this->collector = $collector;
+ $this->params = $params;
+ }
+
+ /**
+ * Can this output type be used?
+ * @return bool
+ */
+ public function canUse() {
+ return true;
+ }
+
+ /**
+ * Log MediaWiki-style profiling data
+ *
+ * @param array $stats Result of Profiler::getFunctionStats()
+ */
+ abstract public function log( array $stats );
+}
diff --git a/includes/profiler/ProfilerSimpleDB.php b/includes/profiler/output/ProfilerOutputDb.php
index 7ef0ad05..76d62d2e 100644
--- a/includes/profiler/ProfilerSimpleDB.php
+++ b/includes/profiler/output/ProfilerOutputDb.php
@@ -22,47 +22,45 @@
*/
/**
- * $wgProfiler['class'] = 'ProfilerSimpleDB';
+ * Logs profiling data into the local DB
*
* @ingroup Profiler
+ * @since 1.25
*/
-class ProfilerSimpleDB extends ProfilerStandard {
- protected function collateOnly() {
- return true;
- }
-
- public function isPersistent() {
- return true;
- }
+class ProfilerOutputDb extends ProfilerOutput {
+ /** @var bool Whether to store host data with profiling calls */
+ private $perHost = false;
- /**
- * Log the whole profiling data into the database.
- */
- public function logData() {
+ public function __construct( Profiler $collector, array $params ) {
+ parent::__construct( $collector, $params );
global $wgProfilePerHost;
- # Do not log anything if database is readonly (bug 5375)
- if ( wfReadOnly() ) {
- return;
+ // Initialize per-host profiling from config, back-compat if available
+ if ( isset( $this->params['perHost'] ) ) {
+ $this->perHost = $this->params['perHost'];
+ } elseif ( $wgProfilePerHost ) {
+ $this->perHost = $wgProfilePerHost;
}
+ }
- if ( $wgProfilePerHost ) {
- $pfhost = wfHostname();
- } else {
- $pfhost = '';
- }
+ public function canUse() {
+ # Do not log anything if database is readonly (bug 5375)
+ return !wfReadOnly();
+ }
- try {
- $this->collateData();
+ public function log( array $stats ) {
+ $pfhost = $this->perHost ? wfHostname() : '';
+ try {
$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 );
+ foreach ( $stats as $data ) {
+ $name = $data['name'];
+ $eventCount = $data['calls'];
+ $timeSum = (float)$data['real'];
$memorySum = (float)$data['memory'];
$name = substr( $name, 0, 255 );
@@ -70,37 +68,22 @@ class ProfilerSimpleDB extends ProfilerStandard {
$timeSum = $timeSum >= 0 ? $timeSum : 0;
$memorySum = $memorySum >= 0 ? $memorySum : 0;
- $dbw->update( 'profiling',
+ $dbw->upsert( 'profiling',
+ array(
+ 'pf_name' => $name,
+ 'pf_count' => $eventCount,
+ 'pf_time' => $timeSum,
+ 'pf_memory' => $memorySum,
+ 'pf_server' => $pfhost
+ ),
+ array( array( 'pf_name', 'pf_server' ) ),
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)";
+ __METHOD__
+ );
}
if ( $useTrx ) {
$dbw->endAtomic( __METHOD__ );
diff --git a/includes/profiler/output/ProfilerOutputDump.php b/includes/profiler/output/ProfilerOutputDump.php
new file mode 100644
index 00000000..bf4b85c2
--- /dev/null
+++ b/includes/profiler/output/ProfilerOutputDump.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Profiler dumping output in xhprof dump file
+ *
+ * This 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 dumping output in xhprof dump file
+ * @ingroup Profiler
+ *
+ * @since 1.25
+ */
+class ProfilerOutputDump extends ProfilerOutput {
+
+ protected $suffix = ".xhprof";
+
+ /**
+ * Can this output type be used?
+ *
+ * @return bool
+ */
+ public function canUse() {
+ if ( empty( $this->params['outputDir'] ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ public function log( array $stats ) {
+ $data = $this->collector->getRawData();
+ $filename = sprintf( "%s/%s.%s%s", $this->params['outputDir'], uniqid(), $this->collector->getProfileID(), $this->suffix );
+ file_put_contents( $filename, serialize( $data ) );
+ }
+}
diff --git a/includes/profiler/output/ProfilerOutputStats.php b/includes/profiler/output/ProfilerOutputStats.php
new file mode 100644
index 00000000..ef6ef7c9
--- /dev/null
+++ b/includes/profiler/output/ProfilerOutputStats.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * ProfilerOutput class that flushes profiling data to the profiling
+ * context's stats buffer.
+ *
+ * This 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
+ */
+
+/**
+ * ProfilerOutput class that flushes profiling data to the profiling
+ * context's stats buffer.
+ *
+ * @ingroup Profiler
+ * @since 1.25
+ */
+class ProfilerOutputStats extends ProfilerOutput {
+
+ /**
+ * Flush profiling data to the current profiling context's stats buffer.
+ *
+ * @param array $stats
+ */
+ public function log( array $stats ) {
+ $contextStats = $this->collector->getContext()->getStats();
+
+ foreach ( $stats as $stat ) {
+ // Sanitize the key
+ $key = str_replace( '::', '.', $stat['name'] );
+ $key = preg_replace( '/[^a-z.]+/i', '_', $key );
+ $key = trim( $key, '_.' );
+
+ // Convert fractional seconds to whole milliseconds
+ $cpu = round( $stat['cpu'] * 1000 );
+ $real = round( $stat['real'] * 1000 );
+
+ $contextStats->increment( "{$key}.calls" );
+ $contextStats->timing( "{$key}.cpu", $cpu );
+ $contextStats->timing( "{$key}.real", $real );
+ }
+ }
+}
diff --git a/includes/profiler/output/ProfilerOutputText.php b/includes/profiler/output/ProfilerOutputText.php
new file mode 100644
index 00000000..67527798
--- /dev/null
+++ b/includes/profiler/output/ProfilerOutputText.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Profiler showing output in page source.
+ *
+ * This 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
+ */
+
+/**
+ * The least sophisticated profiler output class possible, view your source! :)
+ *
+ * @ingroup Profiler
+ * @since 1.25
+ */
+class ProfilerOutputText extends ProfilerOutput {
+ /** @var float Min real time display threshold */
+ protected $thresholdMs;
+
+ function __construct( Profiler $collector, array $params ) {
+ parent::__construct( $collector, $params );
+ $this->thresholdMs = isset( $params['thresholdMs'] )
+ ? $params['thresholdMs']
+ : .25;
+ }
+ public function log( array $stats ) {
+ if ( $this->collector->getTemplated() ) {
+ $out = '';
+
+ // Filter out really tiny entries
+ $min = $this->thresholdMs;
+ $stats = array_filter( $stats, function ( $a ) use ( $min ) {
+ return $a['real'] > $min;
+ } );
+ // Sort descending by time elapsed
+ usort( $stats, function ( $a, $b ) {
+ return $a['real'] < $b['real'];
+ } );
+
+ array_walk( $stats,
+ function ( $item ) use ( &$out ) {
+ $out .= sprintf( "%6.2f%% %3.3f %6d - %s\n",
+ $item['%real'], $item['real'], $item['calls'], $item['name'] );
+ }
+ );
+
+ $contentType = $this->collector->getContentType();
+ if ( PHP_SAPI === 'cli' ) {
+ print "<!--\n{$out}\n-->\n";
+ } elseif ( $contentType === 'text/html' ) {
+ $visible = isset( $this->params['visible'] ) ?
+ $this->params['visible'] : false;
+ if ( $visible ) {
+ print "<pre>{$out}</pre>";
+ } else {
+ print "<!--\n{$out}\n-->\n";
+ }
+ } elseif ( $contentType === 'text/javascript' || $contentType === 'text/css' ) {
+ print "\n/*\n{$out}*/\n";
+ }
+ }
+ }
+}
diff --git a/includes/profiler/output/ProfilerOutputUdp.php b/includes/profiler/output/ProfilerOutputUdp.php
new file mode 100644
index 00000000..7da03c11
--- /dev/null
+++ b/includes/profiler/output/ProfilerOutputUdp.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Profiler sending messages over UDP.
+ *
+ * This 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
+ */
+
+/**
+ * ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon
+ * (see http://git.wikimedia.org/tree/operations%2Fsoftware.git/master/udpprofile)
+ *
+ * @ingroup Profiler
+ * @since 1.25
+ */
+class ProfilerOutputUdp extends ProfilerOutput {
+ /** @var int port to send profiling data to */
+ private $port = 3811;
+
+ /** @var string host to send profiling data to */
+ private $host = '127.0.0.1';
+
+ /** @var string format string for profiling data */
+ private $format = "%s - %d %f %f %f %f %s\n";
+
+ public function __construct( Profiler $collector, array $params ) {
+ parent::__construct( $collector, $params );
+ global $wgUDPProfilerPort, $wgUDPProfilerHost, $wgUDPProfilerFormatString;
+
+ // Initialize port, host, and format from config, back-compat if available
+ if ( isset( $this->params['udpport'] ) ) {
+ $this->port = $this->params['udpport'];
+ } elseif ( $wgUDPProfilerPort ) {
+ $this->port = $wgUDPProfilerPort;
+ }
+
+ if ( isset( $this->params['udphost'] ) ) {
+ $this->host = $this->params['udphost'];
+ } elseif ( $wgUDPProfilerHost ) {
+ $this->host = $wgUDPProfilerHost;
+ }
+
+ if ( isset( $this->params['udpformat'] ) ) {
+ $this->format = $this->params['udpformat'];
+ } elseif ( $wgUDPProfilerFormatString ) {
+ $this->format = $wgUDPProfilerFormatString;
+ }
+ }
+
+ public function canUse() {
+ # Sockets are not enabled
+ return function_exists( 'socket_create' );
+ }
+
+ public function log( array $stats ) {
+ $sock = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
+ $plength = 0;
+ $packet = "";
+ foreach ( $stats as $pfdata ) {
+ $pfline = sprintf( $this->format,
+ $this->collector->getProfileID(),
+ $pfdata['calls'],
+ $pfdata['cpu'] / 1000, // ms => sec
+ 0.0, // sum of CPU^2 for each invocation (unused)
+ $pfdata['real'] / 1000, // ms => sec
+ 0.0, // sum of real^2 for each invocation (unused)
+ $pfdata['name'],
+ $pfdata['memory']
+ );
+ $length = strlen( $pfline );
+ if ( $length + $plength > 1400 ) {
+ socket_sendto( $sock, $packet, $plength, 0, $this->host, $this->port );
+ $packet = "";
+ $plength = 0;
+ }
+ $packet .= $pfline;
+ $plength += $length;
+ }
+ socket_sendto( $sock, $packet, $plength, 0x100, $this->host, $this->port );
+ }
+}
diff --git a/includes/rcfeed/IRCColourfulRCFeedFormatter.php b/includes/rcfeed/IRCColourfulRCFeedFormatter.php
index 02a8d7eb..30be3431 100644
--- a/includes/rcfeed/IRCColourfulRCFeedFormatter.php
+++ b/includes/rcfeed/IRCColourfulRCFeedFormatter.php
@@ -56,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, $rc ) );
+ Hooks::run( 'IRCLineURL', array( &$url, &$query, $rc ) );
$url .= $query;
}
diff --git a/includes/rcfeed/UDPRCFeedEngine.php b/includes/rcfeed/UDPRCFeedEngine.php
index 8554670e..9afae661 100644
--- a/includes/rcfeed/UDPRCFeedEngine.php
+++ b/includes/rcfeed/UDPRCFeedEngine.php
@@ -29,6 +29,7 @@ class UDPRCFeedEngine implements RCFeedEngine {
* @see RCFeedEngine::send
*/
public function send( array $feed, $line ) {
- wfErrorLog( $line, $feed['uri'] );
+ $transport = UDPTransport::newFromString( $feed['uri'] );
+ $transport->emit( $line );
}
}
diff --git a/includes/registration/ExtensionProcessor.php b/includes/registration/ExtensionProcessor.php
new file mode 100644
index 00000000..7f738661
--- /dev/null
+++ b/includes/registration/ExtensionProcessor.php
@@ -0,0 +1,299 @@
+<?php
+
+class ExtensionProcessor implements Processor {
+
+ /**
+ * Keys that should be set to $GLOBALS
+ *
+ * @var array
+ */
+ protected static $globalSettings = array(
+ 'ResourceLoaderSources',
+ 'ResourceLoaderLESSVars',
+ 'ResourceLoaderLESSImportPaths',
+ 'DefaultUserOptions',
+ 'HiddenPrefs',
+ 'GroupPermissions',
+ 'RevokePermissions',
+ 'ImplicitGroups',
+ 'GroupsAddToSelf',
+ 'GroupsRemoveFromSelf',
+ 'AddGroups',
+ 'RemoveGroups',
+ 'AvailableRights',
+ 'ContentHandlers',
+ 'ConfigRegistry',
+ 'RateLimits',
+ 'RecentChangesFlags',
+ 'MediaHandlers',
+ 'ExtensionFunctions',
+ 'ExtensionEntryPointListFiles',
+ 'SpecialPages',
+ 'JobClasses',
+ 'LogTypes',
+ 'LogRestrictions',
+ 'FilterLogTypes',
+ 'LogNames',
+ 'LogHeaders',
+ 'LogActions',
+ 'LogActionsHandlers',
+ 'Actions',
+ 'APIModules',
+ 'APIFormatModules',
+ 'APIMetaModules',
+ 'APIPropModules',
+ 'APIListModules',
+ 'ValidSkinNames',
+ );
+
+ /**
+ * Keys that are part of the extension credits
+ *
+ * @var array
+ */
+ protected static $creditsAttributes = array(
+ 'name',
+ 'namemsg',
+ 'author',
+ 'version',
+ 'url',
+ 'description',
+ 'descriptionmsg',
+ 'license-name',
+ );
+
+ /**
+ * Things that are not 'attributes', but are not in
+ * $globalSettings or $creditsAttributes.
+ *
+ * @var array
+ */
+ protected static $notAttributes = array(
+ 'callback',
+ 'Hooks',
+ 'namespaces',
+ 'ResourceFileModulePaths',
+ 'ResourceModules',
+ 'ResourceModuleSkinStyles',
+ 'ExtensionMessagesFiles',
+ 'MessagesDirs',
+ 'type',
+ 'config',
+ 'ParserTestFiles',
+ 'AutoloadClasses',
+ );
+
+ /**
+ * Stuff that is going to be set to $GLOBALS
+ *
+ * Some keys are pre-set to arrays so we can += to them
+ *
+ * @var array
+ */
+ protected $globals = array(
+ 'wgExtensionMessagesFiles' => array(),
+ 'wgMessagesDirs' => array(),
+ );
+
+ /**
+ * Things that should be define()'d
+ *
+ * @var array
+ */
+ protected $defines = array();
+
+ /**
+ * Things to be called once registration of these extensions are done
+ *
+ * @var callable[]
+ */
+ protected $callbacks = array();
+
+ /**
+ * @var array
+ */
+ protected $credits = array();
+
+ /**
+ * Any thing else in the $info that hasn't
+ * already been processed
+ *
+ * @var array
+ */
+ protected $attributes = array();
+
+ /**
+ * @param string $path
+ * @param array $info
+ * @return array
+ */
+ public function extractInfo( $path, array $info ) {
+ $this->extractConfig( $info );
+ $this->extractHooks( $info );
+ $dir = dirname( $path );
+ $this->extractExtensionMessagesFiles( $dir, $info );
+ $this->extractMessagesDirs( $dir, $info );
+ $this->extractNamespaces( $info );
+ $this->extractResourceLoaderModules( $dir, $info );
+ $this->extractParserTestFiles( $dir, $info );
+ if ( isset( $info['callback'] ) ) {
+ $this->callbacks[] = $info['callback'];
+ }
+
+ $this->extractCredits( $path, $info );
+ foreach ( $info as $key => $val ) {
+ if ( in_array( $key, self::$globalSettings ) ) {
+ $this->storeToArray( "wg$key", $val, $this->globals );
+ // Ignore anything that starts with a @
+ } elseif ( $key[0] !== '@' && !in_array( $key, self::$notAttributes )
+ && !in_array( $key, self::$creditsAttributes )
+ ) {
+ $this->storeToArray( $key, $val, $this->attributes );
+ }
+ }
+ }
+
+ public function getExtractedInfo() {
+ return array(
+ 'globals' => $this->globals,
+ 'defines' => $this->defines,
+ 'callbacks' => $this->callbacks,
+ 'credits' => $this->credits,
+ 'attributes' => $this->attributes,
+ );
+ }
+
+ protected function extractHooks( array $info ) {
+ if ( isset( $info['Hooks'] ) ) {
+ foreach ( $info['Hooks'] as $name => $callable ) {
+ $this->globals['wgHooks'][$name][] = $callable;
+ }
+ }
+ }
+
+ /**
+ * Register namespaces with the appropriate global settings
+ *
+ * @param array $info
+ */
+ protected function extractNamespaces( array $info ) {
+ if ( isset( $info['namespaces'] ) ) {
+ foreach ( $info['namespaces'] as $ns ) {
+ $id = $ns['id'];
+ $this->defines[$ns['constant']] = $id;
+ $this->globals['wgExtraNamespaces'][$id] = $ns['name'];
+ if ( isset( $ns['gender'] ) ) {
+ $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
+ }
+ if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
+ $this->globals['wgNamespacesWithSubpages'][$id] = true;
+ }
+ if ( isset( $ns['content'] ) && $ns['content'] ) {
+ $this->globals['wgContentNamespaces'][] = $id;
+ }
+ if ( isset( $ns['defaultcontentmodel'] ) ) {
+ $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
+ }
+ }
+ }
+ }
+
+ protected function extractResourceLoaderModules( $dir, array $info ) {
+ $defaultPaths = isset( $info['ResourceFileModulePaths'] )
+ ? $info['ResourceFileModulePaths']
+ : false;
+ if ( isset( $defaultPaths['localBasePath'] ) ) {
+ $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
+ }
+
+ foreach ( array( 'ResourceModules', 'ResourceModuleSkinStyles' ) as $setting ) {
+ if ( isset( $info[$setting] ) ) {
+ foreach ( $info[$setting] as $name => $data ) {
+ if ( isset( $data['localBasePath'] ) ) {
+ $data['localBasePath'] = "$dir/{$data['localBasePath']}";
+ }
+ if ( $defaultPaths ) {
+ $data += $defaultPaths;
+ }
+ $this->globals["wg$setting"][$name] = $data;
+ }
+ }
+ }
+ }
+
+ protected function extractExtensionMessagesFiles( $dir, array $info ) {
+ if ( isset( $info['ExtensionMessagesFiles'] ) ) {
+ $this->globals["wgExtensionMessagesFiles"] += array_map( function( $file ) use ( $dir ) {
+ return "$dir/$file";
+ }, $info['ExtensionMessagesFiles'] );
+ }
+ }
+
+ /**
+ * Set message-related settings, which need to be expanded to use
+ * absolute paths
+ *
+ * @param string $dir
+ * @param array $info
+ */
+ protected function extractMessagesDirs( $dir, array $info ) {
+ if ( isset( $info['MessagesDirs'] ) ) {
+ foreach ( $info['MessagesDirs'] as $name => $files ) {
+ foreach ( (array)$files as $file ) {
+ $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
+ }
+ }
+ }
+ }
+
+ protected function extractCredits( $path, array $info ) {
+ $credits = array(
+ 'path' => $path,
+ 'type' => isset( $info['type'] ) ? $info['type'] : 'other',
+ );
+ foreach ( self::$creditsAttributes as $attr ) {
+ if ( isset( $info[$attr] ) ) {
+ $credits[$attr] = $info[$attr];
+ }
+ }
+
+ $this->credits[$credits['name']] = $credits;
+ }
+
+ /**
+ * Set configuration settings
+ * @todo In the future, this should be done via Config interfaces
+ *
+ * @param array $info
+ */
+ protected function extractConfig( array $info ) {
+ if ( isset( $info['config'] ) ) {
+ foreach ( $info['config'] as $key => $val ) {
+ if ( $key[0] !== '@' ) {
+ $this->globals["wg$key"] = $val;
+ }
+ }
+ }
+ }
+
+ protected function extractParserTestFiles( $dir, array $info ) {
+ if ( isset( $info['ParserTestFiles'] ) ) {
+ foreach ( $info['ParserTestFiles'] as $path ) {
+ $this->globals['wgParserTestFiles'][] = "$dir/$path";
+ }
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ * @param array &$array
+ */
+ protected function storeToArray( $name, $value, &$array ) {
+ if ( isset( $array[$name] ) ) {
+ $array[$name] = array_merge_recursive( $array[$name], $value );
+ } else {
+ $array[$name] = $value;
+ }
+ }
+}
diff --git a/includes/registration/ExtensionRegistry.php b/includes/registration/ExtensionRegistry.php
new file mode 100644
index 00000000..2558f7e2
--- /dev/null
+++ b/includes/registration/ExtensionRegistry.php
@@ -0,0 +1,254 @@
+<?php
+
+/**
+ * ExtensionRegistry class
+ *
+ * The Registry loads JSON files, and uses a Processor
+ * to extract information from them. It also registers
+ * classes with the autoloader.
+ *
+ * @since 1.25
+ */
+class ExtensionRegistry {
+
+ /**
+ * @var BagOStuff
+ */
+ protected $cache;
+
+ /**
+ * Array of loaded things, keyed by name, values are credits information
+ *
+ * @var array
+ */
+ private $loaded = array();
+
+ /**
+ * List of paths that should be loaded
+ *
+ * @var array
+ */
+ protected $queued = array();
+
+ /**
+ * Items in the JSON file that aren't being
+ * set as globals
+ *
+ * @var array
+ */
+ protected $attributes = array();
+
+ /**
+ * @var ExtensionRegistry
+ */
+ private static $instance;
+
+ /**
+ * @return ExtensionRegistry
+ */
+ public static function getInstance() {
+ if ( self::$instance === null ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ public function __construct() {
+ // We use a try/catch instead of the $fallback parameter because
+ // we don't want to fail here if $wgObjectCaches is not configured
+ // properly for APC setup
+ try {
+ $this->cache = ObjectCache::newAccelerator( array() );
+ } catch ( MWException $e ) {
+ $this->cache = new EmptyBagOStuff();
+ }
+ }
+
+ /**
+ * @param string $path Absolute path to the JSON file
+ */
+ public function queue( $path ) {
+ global $wgExtensionInfoMTime;
+
+ $mtime = $wgExtensionInfoMTime;
+ if ( $mtime === false ) {
+ if ( file_exists( $path ) ) {
+ $mtime = filemtime( $path );
+ } else {
+ throw new Exception( "$path does not exist!" );
+ }
+ if ( !$mtime ) {
+ $err = error_get_last();
+ throw new Exception( "Couldn't stat $path: {$err['message']}" );
+ }
+ }
+ $this->queued[$path] = $mtime;
+ }
+
+ public function loadFromQueue() {
+ if ( !$this->queued ) {
+ return;
+ }
+
+ // See if this queue is in APC
+ $key = wfMemcKey( 'registration', md5( json_encode( $this->queued ) ) );
+ $data = $this->cache->get( $key );
+ if ( $data ) {
+ $this->exportExtractedData( $data );
+ } else {
+ $data = $this->readFromQueue( $this->queued );
+ $this->exportExtractedData( $data );
+ // Do this late since we don't want to extract it since we already
+ // did that, but it should be cached
+ $data['globals']['wgAutoloadClasses'] += $data['autoload'];
+ unset( $data['autoload'] );
+ $this->cache->set( $key, $data, 60 * 60 * 24 );
+ }
+ $this->queued = array();
+ }
+
+ /**
+ * Process a queue of extensions and return their extracted data
+ *
+ * @param array $queue keys are filenames, values are ignored
+ * @return array extracted info
+ * @throws Exception
+ */
+ public function readFromQueue( array $queue ) {
+ $data = array( 'globals' => array( 'wgAutoloadClasses' => array() ) );
+ $autoloadClasses = array();
+ $processor = new ExtensionProcessor();
+ foreach ( $queue as $path => $mtime ) {
+ $json = file_get_contents( $path );
+ $info = json_decode( $json, /* $assoc = */ true );
+ if ( !is_array( $info ) ) {
+ throw new Exception( "$path is not a valid JSON file." );
+ }
+ $autoload = $this->processAutoLoader( dirname( $path ), $info );
+ // Set up the autoloader now so custom processors will work
+ $GLOBALS['wgAutoloadClasses'] += $autoload;
+ $autoloadClasses += $autoload;
+ $processor->extractInfo( $path, $info );
+ }
+ $data = $processor->getExtractedInfo();
+ // Need to set this so we can += to it later
+ $data['globals']['wgAutoloadClasses'] = array();
+ foreach ( $data['credits'] as $credit ) {
+ $data['globals']['wgExtensionCredits'][$credit['type']][] = $credit;
+ }
+ $data['autoload'] = $autoloadClasses;
+ return $data;
+ }
+
+ protected function exportExtractedData( array $info ) {
+ foreach ( $info['globals'] as $key => $val ) {
+ if ( !isset( $GLOBALS[$key] ) || !$GLOBALS[$key] ) {
+ $GLOBALS[$key] = $val;
+ } elseif ( $key === 'wgHooks' || $key === 'wgExtensionCredits' ) {
+ // Special case $wgHooks and $wgExtensionCredits, which require a recursive merge.
+ // Ideally it would have been taken care of in the first if block though.
+ $GLOBALS[$key] = array_merge_recursive( $GLOBALS[$key], $val );
+ } elseif ( $key === 'wgGroupPermissions' ) {
+ // First merge individual groups
+ foreach ( $GLOBALS[$key] as $name => &$groupVal ) {
+ if ( isset( $val[$name] ) ) {
+ $groupVal += $val[$name];
+ }
+ }
+ // Now merge groups that didn't exist yet
+ $GLOBALS[$key] += $val;
+ } elseif ( is_array( $GLOBALS[$key] ) && is_array( $val ) ) {
+ $GLOBALS[$key] = array_merge( $val, $GLOBALS[$key] );
+ } // else case is a config setting where it has already been overriden, so don't set it
+ }
+ foreach ( $info['defines'] as $name => $val ) {
+ define( $name, $val );
+ }
+ foreach ( $info['callbacks'] as $cb ) {
+ call_user_func( $cb );
+ }
+
+ $this->loaded += $info['credits'];
+
+ if ( $info['attributes'] ) {
+ if ( !$this->attributes ) {
+ $this->attributes = $info['attributes'];
+ } else {
+ $this->attributes = array_merge_recursive( $this->attributes, $info['attributes'] );
+ }
+ }
+ }
+
+ /**
+ * Loads and processes the given JSON file without delay
+ *
+ * If some extensions are already queued, this will load
+ * those as well.
+ *
+ * @param string $path Absolute path to the JSON file
+ */
+ public function load( $path ) {
+ $this->loadFromQueue(); // First clear the queue
+ $this->queue( $path );
+ $this->loadFromQueue();
+ }
+
+ /**
+ * Whether a thing has been loaded
+ * @param string $name
+ * @return bool
+ */
+ public function isLoaded( $name ) {
+ return isset( $this->loaded[$name] );
+ }
+
+ /**
+ * @param string $name
+ * @return array
+ */
+ public function getAttribute( $name ) {
+ if ( isset( $this->attributes[$name] ) ) {
+ return $this->attributes[$name];
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Get information about all things
+ *
+ * @return array
+ */
+ public function getAllThings() {
+ return $this->loaded;
+ }
+
+ /**
+ * Mark a thing as loaded
+ *
+ * @param string $name
+ * @param array $credits
+ */
+ protected function markLoaded( $name, array $credits ) {
+ $this->loaded[$name] = $credits;
+ }
+
+ /**
+ * Register classes with the autoloader
+ *
+ * @param string $dir
+ * @param array $info
+ * @return array
+ */
+ protected function processAutoLoader( $dir, array $info ) {
+ if ( isset( $info['AutoloadClasses'] ) ) {
+ // Make paths absolute, relative to the JSON file
+ return array_map( function( $file ) use ( $dir ) {
+ return "$dir/$file";
+ }, $info['AutoloadClasses'] );
+ } else {
+ return array();
+ }
+ }
+}
diff --git a/includes/registration/Processor.php b/includes/registration/Processor.php
new file mode 100644
index 00000000..e930fd3e
--- /dev/null
+++ b/includes/registration/Processor.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Processors read associated arrays and register
+ * whatever is required
+ *
+ * @since 1.25
+ */
+interface Processor {
+
+ /**
+ * Main entry point, processes the information
+ * provided.
+ * Callers should call "callback" after calling
+ * this function.
+ *
+ * @param string $path Absolute path of JSON file
+ * @param array $info
+ * @return array "credits" information to store
+ */
+ public function extractInfo( $path, array $info );
+
+ /**
+ * @return array With 'globals', 'defines', 'callbacks', 'credits' keys.
+ */
+ public function getExtractedInfo();
+}
diff --git a/includes/resourceloader/DerivativeResourceLoaderContext.php b/includes/resourceloader/DerivativeResourceLoaderContext.php
index d114d7ed..5784f2a0 100644
--- a/includes/resourceloader/DerivativeResourceLoaderContext.php
+++ b/includes/resourceloader/DerivativeResourceLoaderContext.php
@@ -126,6 +126,7 @@ class DerivativeResourceLoaderContext extends ResourceLoaderContext {
public function setUser( $user ) {
$this->user = $user;
$this->hash = null;
+ $this->userObj = null;
}
public function getDebug() {
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 4f1414bc..150ccd07 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -35,26 +35,47 @@ class ResourceLoader {
/** @var bool */
protected static $debugMode = null;
- /** @var array Module name/ResourceLoaderModule object pairs */
+ /** @var array */
+ private static $lessVars = null;
+
+ /**
+ * Module name/ResourceLoaderModule object pairs
+ * @var array
+ */
protected $modules = array();
- /** @var array Associative array mapping module name to info associative array */
+ /**
+ * Associative array mapping module name to info associative array
+ * @var array
+ */
protected $moduleInfos = array();
/** @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', .. ), .. )
+ * 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 array
*/
protected $testModuleNames = array();
- /** @var array E.g. array( 'source-id' => 'http://.../load.php' ) */
+ /**
+ * E.g. array( 'source-id' => 'http://.../load.php' )
+ * @var array
+ */
protected $sources = array();
- /** @var bool */
- protected $hasErrors = false;
+ /**
+ * Errors accumulated during current respond() call.
+ * @var array
+ */
+ protected $errors = array();
+
+ /**
+ * @var MessageBlobStore
+ */
+ protected $blobStore;
/**
* Load information stored in the database about modules.
@@ -130,7 +151,7 @@ class ResourceLoader {
foreach ( array_keys( $modulesWithoutMessages ) as $name ) {
$module = $this->getModule( $name );
if ( $module ) {
- $module->setMsgBlobMtime( $lang, 0 );
+ $module->setMsgBlobMtime( $lang, 1 );
}
}
}
@@ -152,12 +173,10 @@ class ResourceLoader {
* @return string Filtered data, or a comment containing an error message
*/
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' ) ) ) {
- wfProfileOut( __METHOD__ );
return $data;
}
@@ -168,7 +187,6 @@ class ResourceLoader {
$cacheEntry = $cache->get( $key );
if ( is_string( $cacheEntry ) ) {
wfIncrStats( "rl-$filter-cache-hits" );
- wfProfileOut( __METHOD__ );
return $cacheEntry;
}
@@ -199,13 +217,9 @@ class ResourceLoader {
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": minification failed: $e" );
- $this->hasErrors = true;
- // Return exception as a comment
- $result = self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
- wfProfileOut( __METHOD__ );
-
return $result;
}
@@ -218,8 +232,6 @@ class ResourceLoader {
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' );
@@ -236,14 +248,14 @@ class ResourceLoader {
// Register core modules
$this->register( include "$IP/resources/Resources.php" );
// Register extension modules
- wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
+ Hooks::run( 'ResourceLoaderRegisterModules', array( &$this ) );
$this->register( $config->get( 'ResourceModules' ) );
if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
$this->registerTestModules();
}
- wfProfileOut( __METHOD__ );
+ $this->setMessageBlobStore( new MessageBlobStore() );
}
/**
@@ -254,6 +266,14 @@ class ResourceLoader {
}
/**
+ * @param MessageBlobStore $blobStore
+ * @since 1.25
+ */
+ public function setMessageBlobStore( MessageBlobStore $blobStore ) {
+ $this->blobStore = $blobStore;
+ }
+
+ /**
* Register a module with the ResourceLoader system.
*
* @param mixed $name Name of module as a string or List of name/object pairs as an array
@@ -267,14 +287,12 @@ class ResourceLoader {
* not registered
*/
public function register( $name, $info = null ) {
- wfProfileIn( __METHOD__ );
// Allow multiple modules to be registered in one call
$registrations = is_array( $name ) ? $name : array( $name => $info );
foreach ( $registrations as $name => $info ) {
// Disallow duplicate registrations
if ( isset( $this->moduleInfos[$name] ) ) {
- wfProfileOut( __METHOD__ );
// A module has already been registered by this name
throw new MWException(
'ResourceLoader duplicate registration error. ' .
@@ -284,7 +302,6 @@ class ResourceLoader {
// Check $name for validity
if ( !self::isValidModuleName( $name ) ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "ResourceLoader module name '$name' is invalid, "
. "see ResourceLoader::isValidModuleName()" );
}
@@ -298,7 +315,6 @@ class ResourceLoader {
// 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 ) . ')'
@@ -323,19 +339,16 @@ class ResourceLoader {
} elseif ( isset( $skinStyles['+' . $name] ) ) {
$paths = (array)$skinStyles['+' . $name];
$styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
- $this->moduleInfos[$name]['skinStyles']['default'] :
+ (array)$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.
+ // from the module we're modifying, which come from the base definition.
list( $localBasePath, $remoteBasePath ) =
ResourceLoaderFileModule::extractBasePaths( $skinStyles );
- list( $localBasePath, $remoteBasePath ) =
- ResourceLoaderFileModule::extractBasePaths( $paths, $localBasePath, $remoteBasePath );
foreach ( $paths as $path ) {
$styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
@@ -346,7 +359,6 @@ class ResourceLoader {
}
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -360,13 +372,11 @@ class ResourceLoader {
. 'Edit your <code>LocalSettings.php</code> to enable it.' );
}
- wfProfileIn( __METHOD__ );
-
// Get core test suites
$testModules = array();
$testModules['qunit'] = array();
// Get other test suites (e.g. from extensions)
- wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
+ Hooks::run( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
// Add the testrunner (which configures QUnit) to the dependencies.
// Since it must be ready before any of the test suites are executed.
@@ -389,7 +399,6 @@ class ResourceLoader {
$this->testModuleNames[$id] = array_keys( $testModules[$id] );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -464,6 +473,17 @@ class ResourceLoader {
}
/**
+ * Check whether a ResourceLoader module is registered
+ *
+ * @since 1.25
+ * @param string $name
+ * @return bool
+ */
+ public function isModuleRegistered( $name ) {
+ return isset( $this->moduleInfos[$name] );
+ }
+
+ /**
* Get the ResourceLoaderModule object for a given module name.
*
* If an array of module parameters exists but a ResourceLoaderModule object has not
@@ -568,9 +588,6 @@ class ResourceLoader {
// See http://bugs.php.net/bug.php?id=36514
ob_start();
- wfProfileIn( __METHOD__ );
- $errors = '';
-
// Find out which modules are missing and instantiate the others
$modules = array();
$missing = array();
@@ -581,10 +598,7 @@ class ResourceLoader {
// This is a security issue, see bug 34907.
if ( $module->getGroup() === 'private' ) {
wfDebugLog( 'resourceloader', __METHOD__ . ": request for private module '$name' denied" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $errors .= self::makeComment( "Cannot show private module \"$name\"" );
-
+ $this->errors[] = "Cannot show private module \"$name\"";
continue;
}
$modules[$name] = $module;
@@ -599,13 +613,9 @@ class ResourceLoader {
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": preloading module info failed: $e" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $errors .= self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
- wfProfileIn( __METHOD__ . '-getModifiedTime' );
-
// To send Last-Modified and support If-Modified-Since, we need to detect
// the last modified time
$mtime = wfTimestamp( TS_UNIX, $this->config->get( 'CacheEpoch' ) );
@@ -619,36 +629,27 @@ class ResourceLoader {
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": calculating maximum modified time failed: $e" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $errors .= self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
}
- wfProfileOut( __METHOD__ . '-getModifiedTime' );
-
// If there's an If-Modified-Since header, respond with a 304 appropriately
if ( $this->tryRespondLastModified( $context, $mtime ) ) {
- wfProfileOut( __METHOD__ );
return; // output handled (buffers cleared)
}
// Generate a response
$response = $this->makeModuleResponse( $context, $modules, $missing );
- // Prepend comments indicating exceptions
- $response = $errors . $response;
-
// Capture any PHP warnings from the output buffer and append them to the
- // response in a comment if we're in debug mode.
+ // error list if we're in debug mode.
if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
- $response = self::makeComment( $warnings ) . $response;
- $this->hasErrors = true;
+ $this->errors[] = $warnings;
}
// Save response to file cache unless there are errors
- if ( isset( $fileCache ) && !$errors && !count( $missing ) ) {
- // Cache single modules...and other requests if there are enough hits
+ if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
+ // Cache single modules and images...and other requests if there are enough hits
if ( ResourceFileCache::useFileCache( $context ) ) {
if ( $fileCache->isCacheWorthy() ) {
$fileCache->saveText( $response );
@@ -659,20 +660,37 @@ class ResourceLoader {
}
// Send content type and cache related headers
- $this->sendResponseHeaders( $context, $mtime, $this->hasErrors );
+ $this->sendResponseHeaders( $context, $mtime, (bool)$this->errors );
// Remove the output buffer and output the response
ob_end_clean();
+
+ if ( $context->getImageObj() && $this->errors ) {
+ // We can't show both the error messages and the response when it's an image.
+ $errorText = '';
+ foreach ( $this->errors as $error ) {
+ $errorText .= $error . "\n";
+ }
+ $response = $errorText;
+ } elseif ( $this->errors ) {
+ // Prepend comments indicating errors
+ $errorText = '';
+ foreach ( $this->errors as $error ) {
+ $errorText .= self::makeComment( $error );
+ }
+ $response = $errorText . $response;
+ }
+
+ $this->errors = array();
echo $response;
- wfProfileOut( __METHOD__ );
}
/**
* Send content type and last modified headers to the client.
* @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
+ * @param bool $errors Whether there are errors in the response
* @return void
*/
protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) {
@@ -689,7 +707,14 @@ class ResourceLoader {
$maxage = $rlMaxage['versioned']['client'];
$smaxage = $rlMaxage['versioned']['server'];
}
- if ( $context->getOnly() === 'styles' ) {
+ if ( $context->getImageObj() ) {
+ // Output different headers if we're outputting textual errors.
+ if ( $errors ) {
+ header( 'Content-Type: text/plain; charset=utf-8' );
+ } else {
+ $context->getImageObj()->sendResponseHeaders( $context );
+ }
+ } elseif ( $context->getOnly() === 'styles' ) {
header( 'Content-Type: text/css; charset=utf-8' );
header( 'Access-Control-Allow-Origin: *' );
} else {
@@ -813,15 +838,26 @@ class ResourceLoader {
* Handle exception display.
*
* @param Exception $e Exception to be shown to the user
- * @return string Sanitized text that can be returned to the user
+ * @return string Sanitized text in a CSS/JS comment that can be returned to the user
*/
public static function formatException( $e ) {
+ return self::makeComment( self::formatExceptionNoComment( $e ) );
+ }
+
+ /**
+ * Handle exception display.
+ *
+ * @since 1.25
+ * @param Exception $e Exception to be shown to the user
+ * @return string Sanitized text that can be returned to the user
+ */
+ protected static function formatExceptionNoComment( $e ) {
global $wgShowExceptionDetails;
if ( $wgShowExceptionDetails ) {
- return self::makeComment( $e->__toString() );
+ return $e->__toString();
} else {
- return self::makeComment( wfMessage( 'internalerror' )->text() );
+ return wfMessage( 'internalerror' )->text();
}
}
@@ -837,30 +873,37 @@ class ResourceLoader {
array $modules, array $missing = array()
) {
$out = '';
- $exceptions = '';
$states = array();
if ( !count( $modules ) && !count( $missing ) ) {
- return "/* This file is the Web entry point for MediaWiki's ResourceLoader:
+ return <<<MESSAGE
+/* 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. */";
+ no modules were requested. Max made me put this here. */
+MESSAGE;
}
- wfProfileIn( __METHOD__ );
+ $image = $context->getImageObj();
+ if ( $image ) {
+ $data = $image->getImageData( $context );
+ if ( $data === false ) {
+ $data = '';
+ $this->errors[] = 'Image generation failed';
+ }
+ return $data;
+ }
// Pre-fetch blobs
if ( $context->shouldIncludeMessages() ) {
try {
- $blobs = MessageBlobStore::getInstance()->get( $this, $modules, $context->getLanguage() );
+ $blobs = $this->blobStore->get( $this, $modules, $context->getLanguage() );
} catch ( Exception $e ) {
MWExceptionHandler::logException( $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 );
+ $this->errors[] = self::formatExceptionNoComment( $e );
}
} else {
$blobs = array();
@@ -877,7 +920,6 @@ class ResourceLoader {
* @var $module ResourceLoaderModule
*/
- wfProfileIn( __METHOD__ . '-' . $name );
try {
$scripts = '';
if ( $context->shouldIncludeScripts() ) {
@@ -964,28 +1006,33 @@ class ResourceLoader {
case 'messages':
$out .= self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) );
break;
+ case 'templates':
+ $out .= Xml::encodeJsCall(
+ 'mw.templates.set',
+ array( $name, (object)$module->getTemplates() ),
+ ResourceLoader::inDebugMode()
+ );
+ break;
default:
$out .= self::makeLoaderImplementScript(
$name,
$scripts,
$styles,
- new XmlJsCode( $messagesBlob )
+ new XmlJsCode( $messagesBlob ),
+ $module->getTemplates()
);
break;
}
} catch ( Exception $e ) {
MWExceptionHandler::logException( $e );
wfDebugLog( 'resourceloader', __METHOD__ . ": generating module package failed: $e" );
- $this->hasErrors = true;
- // Add exception to the output as a comment
- $exceptions .= self::formatException( $e );
+ $this->errors[] = self::formatExceptionNoComment( $e );
// Respond to client with error-state instead of module implementation
$states[$name] = 'error';
unset( $modules[$name] );
}
$isRaw |= $module->isRaw();
- wfProfileOut( __METHOD__ . '-' . $name );
}
// Update module states
@@ -1004,9 +1051,8 @@ class ResourceLoader {
}
} else {
if ( count( $states ) ) {
- $exceptions .= self::makeComment(
- 'Problematic modules: ' . FormatJson::encode( $states, ResourceLoader::inDebugMode() )
- );
+ $this->errors[] = 'Problematic modules: ' .
+ FormatJson::encode( $states, ResourceLoader::inDebugMode() );
}
}
@@ -1018,8 +1064,7 @@ class ResourceLoader {
}
}
- wfProfileOut( __METHOD__ );
- return $exceptions . $out;
+ return $out;
}
/* Static Methods */
@@ -1034,30 +1079,32 @@ class ResourceLoader {
* @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.
+ * @param array $templates Keys are name of templates and values are the source of
+ * the template.
* @throws MWException
* @return string
*/
- public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
+ public static function makeLoaderImplementScript( $name, $scripts, $styles,
+ $messages, $templates
+ ) {
if ( is_string( $scripts ) ) {
$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.' );
}
- return Xml::encodeJsCall(
- 'mw.loader.implement',
- array(
- $name,
- $scripts,
- // Force objects. mw.loader.implement requires them to be javascript objects.
- // Although these variables are associative arrays, which become javascript
- // objects through json_encode. In many cases they will be empty arrays, and
- // PHP/json_encode() consider empty arrays to be numerical arrays and
- // output javascript "[]" instead of "{}". This fixes that.
- (object)$styles,
- (object)$messages
- ),
- ResourceLoader::inDebugMode()
+ // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
+ // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
+ // of "{}". Force them to objects.
+ $module = array(
+ $name,
+ $scripts,
+ (object) $styles,
+ (object) $messages,
+ (object) $templates,
);
+ self::trimArray( $module );
+
+ return Xml::encodeJsCall( 'mw.loader.implement', $module, ResourceLoader::inDebugMode() );
}
/**
@@ -1164,6 +1211,40 @@ class ResourceLoader {
);
}
+ private static function isEmptyObject( stdClass $obj ) {
+ foreach ( $obj as $key => &$value ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove empty values from the end of an array.
+ *
+ * Values considered empty:
+ *
+ * - null
+ * - array()
+ * - new XmlJsCode( '{}' )
+ * - new stdClass() // (object) array()
+ *
+ * @param Array $array
+ */
+ private static function trimArray( Array &$array ) {
+ $i = count( $array );
+ while ( $i-- ) {
+ if ( $array[$i] === null
+ || $array[$i] === array()
+ || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
+ || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
+ ) {
+ unset( $array[$i] );
+ } else {
+ break;
+ }
+ }
+ }
+
/**
* Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
@@ -1195,16 +1276,37 @@ class ResourceLoader {
$dependencies = null, $group = null, $source = null, $skip = null
) {
if ( is_array( $name ) ) {
+ // Build module name index
+ $index = array();
+ foreach ( $name as $i => &$module ) {
+ $index[$module[0]] = $i;
+ }
+
+ // Transform dependency names into indexes when possible, they will be resolved by
+ // mw.loader.register on the other end
+ foreach ( $name as &$module ) {
+ if ( isset( $module[2] ) ) {
+ foreach ( $module[2] as &$dependency ) {
+ if ( isset( $index[$dependency] ) ) {
+ $dependency = $index[$dependency];
+ }
+ }
+ }
+ }
+
+ array_walk( $name, array( 'self', 'trimArray' ) );
+
return Xml::encodeJsCall(
'mw.loader.register',
array( $name ),
ResourceLoader::inDebugMode()
);
} else {
- $version = (int)$version > 1 ? (int)$version : 1;
+ $registration = array( $name, $version, $dependencies, $group, $source, $skip );
+ self::trimArray( $registration );
return Xml::encodeJsCall(
'mw.loader.register',
- array( $name, $version, $dependencies, $group, $source, $skip ),
+ $registration,
ResourceLoader::inDebugMode()
);
}
@@ -1466,6 +1568,9 @@ class ResourceLoader {
// 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.
+ if ( !class_exists( 'lessc' ) ) {
+ throw new MWException( 'MediaWiki requires the lessphp compiler' );
+ }
if ( !function_exists( 'ctype_digit' ) ) {
throw new MWException( 'lessc requires the Ctype extension' );
}
@@ -1488,9 +1593,13 @@ class ResourceLoader {
* @return array Map of variable names to string CSS values.
*/
public static function getLessVars( Config $config ) {
- $lessVars = $config->get( 'ResourceLoaderLESSVars' );
- // Sort by key to ensure consistent hashing for cache lookups.
- ksort( $lessVars );
- return $lessVars;
+ if ( !self::$lessVars ) {
+ $lessVars = $config->get( 'ResourceLoaderLESSVars' );
+ Hooks::run( 'ResourceLoaderGetLessVars', array( &$lessVars ) );
+ // Sort by key to ensure consistent hashing for cache lookups.
+ ksort( $lessVars );
+ self::$lessVars = $lessVars;
+ }
+ return self::$lessVars;
}
}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index 7af7b898..a6a7d347 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -41,6 +41,11 @@ class ResourceLoaderContext {
protected $version;
protected $hash;
protected $raw;
+ protected $image;
+ protected $variant;
+ protected $format;
+ protected $userObj;
+ protected $imageObj;
/* Methods */
@@ -65,6 +70,10 @@ class ResourceLoaderContext {
$this->only = $request->getVal( 'only' );
$this->version = $request->getVal( 'version' );
$this->raw = $request->getFuzzyBool( 'raw' );
+ // Image requests
+ $this->image = $request->getVal( 'image' );
+ $this->variant = $request->getVal( 'variant' );
+ $this->format = $request->getVal( 'format' );
$skinnames = Skin::getSkinNames();
// If no skin is specified, or we don't recognize the skin, use the default skin
@@ -179,6 +188,31 @@ class ResourceLoaderContext {
}
/**
+ * Get the possibly-cached User object for the specified username
+ *
+ * @since 1.25
+ * @return User|bool false if a valid object cannot be created
+ */
+ public function getUserObj() {
+ if ( $this->userObj === null ) {
+ $username = $this->getUser();
+ if ( $username ) {
+ // Optimize: Avoid loading a new User object if possible
+ global $wgUser;
+ if ( is_object( $wgUser ) && $wgUser->getName() === $username ) {
+ $this->userObj = $wgUser;
+ } else {
+ $this->userObj = User::newFromName( $username );
+ }
+ } else {
+ $this->userObj = new User; // Anonymous user
+ }
+ }
+
+ return $this->userObj;
+ }
+
+ /**
* @return bool
*/
public function getDebug() {
@@ -207,6 +241,62 @@ class ResourceLoaderContext {
}
/**
+ * @return string|null
+ */
+ public function getImage() {
+ return $this->image;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getVariant() {
+ return $this->variant;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getFormat() {
+ return $this->format;
+ }
+
+ /**
+ * If this is a request for an image, get the ResourceLoaderImage object.
+ *
+ * @since 1.25
+ * @return ResourceLoaderImage|bool false if a valid object cannot be created
+ */
+ public function getImageObj() {
+ if ( $this->imageObj === null ) {
+ $this->imageObj = false;
+
+ if ( !$this->image ) {
+ return $this->imageObj;
+ }
+
+ $modules = $this->getModules();
+ if ( count( $modules ) !== 1 ) {
+ return $this->imageObj;
+ }
+
+ $module = $this->getResourceLoader()->getModule( $modules[0] );
+ if ( !$module || !$module instanceof ResourceLoaderImageModule ) {
+ return $this->imageObj;
+ }
+
+ $image = $module->getImage( $this->image );
+ if ( !$image ) {
+ return $this->imageObj;
+ }
+
+ $this->imageObj = $image;
+ }
+
+ return $this->imageObj;
+ }
+
+ /**
* @return bool
*/
public function shouldIncludeScripts() {
@@ -234,6 +324,7 @@ class ResourceLoaderContext {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
$this->getLanguage(), $this->getDirection(), $this->getSkin(), $this->getUser(),
+ $this->getImage(), $this->getVariant(), $this->getFormat(),
$this->getDebug(), $this->getOnly(), $this->getVersion()
) );
}
diff --git a/includes/resourceloader/ResourceLoaderEditToolbarModule.php b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
index 2e07911c..d79174cd 100644
--- a/includes/resourceloader/ResourceLoaderEditToolbarModule.php
+++ b/includes/resourceloader/ResourceLoaderEditToolbarModule.php
@@ -32,6 +32,7 @@ class ResourceLoaderEditToolbarModule extends ResourceLoaderFileModule {
*
* @param string $value
* @return string
+ * @throws Exception
*/
private static function cssSerializeString( $value ) {
if ( strstr( $value, "\0" ) ) {
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index dc8b14a2..671098e1 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -34,6 +34,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/** @var string Remote base path, see __construct() */
protected $remoteBasePath = '';
+ /** @var array Saves a list of the templates named by the modules. */
+ protected $templates = array();
+
/**
* @var array List of paths to JavaScript files to always include
* @par Usage:
@@ -171,7 +174,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* to $wgResourceBasePath
*
* Below is a description for the $options array:
- * @throws MWException
+ * @throws InvalidArgumentException
* @par Construction options:
* @code
* array(
@@ -199,6 +202,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* 'loaderScripts' => [file path string or array of file path strings],
* // Modules which must be loaded before this module
* 'dependencies' => [module name string or array of module name strings],
+ * 'templates' => array(
+ * [template alias with file.ext] => [file path to a template file],
+ * ),
* // Styles to always load
* 'styles' => [file path string or array of file path strings],
* // Styles to include in specific skin contexts
@@ -223,6 +229,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$localBasePath = null,
$remoteBasePath = null
) {
+ // Flag to decide whether to automagically add the mediawiki.template module
+ $hasTemplates = false;
// localBasePath and remoteBasePath both have unbelievably long fallback chains
// and need to be handled separately.
list( $this->localBasePath, $this->remoteBasePath ) =
@@ -238,19 +246,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
case 'styles':
$this->{$member} = (array)$option;
break;
+ case 'templates':
+ $hasTemplates = true;
+ $this->{$member} = (array)$option;
+ break;
// Collated lists of file paths
case 'languageScripts':
case 'skinScripts':
case 'skinStyles':
if ( !is_array( $option ) ) {
- throw new MWException(
+ throw new InvalidArgumentException(
"Invalid collated file path list error. " .
"'$option' given, array expected."
);
}
foreach ( $option as $key => $value ) {
if ( !is_string( $key ) ) {
- throw new MWException(
+ throw new InvalidArgumentException(
"Invalid collated file path list key error. " .
"'$key' given, string expected."
);
@@ -281,6 +293,21 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
}
}
+ if ( $hasTemplates ) {
+ $this->dependencies[] = 'mediawiki.template';
+ // Ensure relevant template compiler module gets loaded
+ foreach ( $this->templates as $alias => $templatePath ) {
+ if ( is_int( $alias ) ) {
+ $alias = $templatePath;
+ }
+ $suffix = explode( '.', $alias );
+ $suffix = end( $suffix );
+ $compilerModule = 'mediawiki.template.' . $suffix;
+ if ( $suffix !== 'html' && !in_array( $compilerModule, $this->dependencies ) ) {
+ $this->dependencies[] = $compilerModule;
+ }
+ }
+ }
}
/**
@@ -304,7 +331,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
// 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 ( $localBasePath === null ) {
+ $localBasePath = $IP;
+ }
if ( $remoteBasePath === null ) {
$remoteBasePath = $wgResourceBasePath;
}
@@ -466,8 +495,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Get the skip function.
- *
- * @return string|null
+ * @return null|string
+ * @throws MWException
*/
public function getSkipFunction() {
if ( !$this->skipFunction ) {
@@ -510,7 +539,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
if ( isset( $this->modifiedTime[$context->getHash()] ) ) {
return $this->modifiedTime[$context->getHash()];
}
- wfProfileIn( __METHOD__ );
$files = array();
@@ -533,8 +561,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$files = array_merge(
$files,
$this->scripts,
+ $this->templates,
$context->getDebug() ? $this->debugScripts : array(),
- self::tryForKey( $this->languageScripts, $context->getLanguage() ),
+ $this->getLanguageScripts( $context->getLanguage() ),
self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ),
$this->loaderScripts
);
@@ -544,18 +573,19 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$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() ) );
+ // Filter out any duplicates from getFileDependencies() and others.
+ // Most commonly introduced by compileLessFile(), which always includes the
+ // entry point Less file we already know about.
+ $files = array_values( array_unique( $files ) );
// 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()];
}
- wfProfileIn( __METHOD__ . '-filemtime' );
$filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
- wfProfileOut( __METHOD__ . '-filemtime' );
$this->modifiedTime[$context->getHash()] = max(
$filesMtime,
@@ -563,7 +593,6 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$this->getDefinitionMtime( $context )
);
- wfProfileOut( __METHOD__ );
return $this->modifiedTime[$context->getHash()];
}
@@ -574,9 +603,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* @return array
*/
public function getDefinitionSummary( ResourceLoaderContext $context ) {
- $summary = array(
- 'class' => get_class( $this ),
- );
+ $summary = parent::getDefinitionSummary( $context );
foreach ( array(
'scripts',
'debugScripts',
@@ -588,6 +615,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
'dependencies',
'messages',
'targets',
+ 'templates',
'group',
'position',
'skipFunction',
@@ -698,7 +726,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected function getScriptFiles( ResourceLoaderContext $context ) {
$files = array_merge(
$this->scripts,
- self::tryForKey( $this->languageScripts, $context->getLanguage() ),
+ $this->getLanguageScripts( $context->getLanguage() ),
self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' )
);
if ( $context->getDebug() ) {
@@ -709,6 +737,29 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * Get the set of language scripts for the given language,
+ * possibly using a fallback language.
+ *
+ * @param string $lang
+ * @return array
+ */
+ private function getLanguageScripts( $lang ) {
+ $scripts = self::tryForKey( $this->languageScripts, $lang );
+ if ( $scripts ) {
+ return $scripts;
+ }
+ $fallbacks = Language::getFallbacksFor( $lang );
+ foreach ( $fallbacks as $lang ) {
+ $scripts = self::tryForKey( $this->languageScripts, $lang );
+ if ( $scripts ) {
+ return $scripts;
+ }
+ }
+
+ return array();
+ }
+
+ /**
* Get a list of file paths for all styles in this module, in order of proper inclusion.
*
* @param ResourceLoaderContext $context
@@ -934,4 +985,30 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected function getLessCompiler( ResourceLoaderContext $context = null ) {
return ResourceLoader::getLessCompiler( $this->getConfig() );
}
+
+ /**
+ * Takes named templates by the module and returns an array mapping.
+ * @return array of templates mapping template alias to content
+ * @throws MWException
+ */
+ public function getTemplates() {
+ $templates = array();
+
+ foreach ( $this->templates as $alias => $templatePath ) {
+ // Alias is optional
+ if ( is_int( $alias ) ) {
+ $alias = $templatePath;
+ }
+ $localPath = $this->getLocalPath( $templatePath );
+ if ( file_exists( $localPath ) ) {
+ $content = file_get_contents( $localPath );
+ $templates[$alias] = $content;
+ } else {
+ $msg = __METHOD__ . ": template file not found: \"$localPath\"";
+ wfDebugLog( 'resourceloader', $msg );
+ throw new MWException( $msg );
+ }
+ }
+ return $templates;
+ }
}
diff --git a/includes/resourceloader/ResourceLoaderImage.php b/includes/resourceloader/ResourceLoaderImage.php
new file mode 100644
index 00000000..12d1e827
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderImage.php
@@ -0,0 +1,388 @@
+<?php
+/**
+ * Class encapsulating an image used in a ResourceLoaderImageModule.
+ *
+ * This 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 encapsulating an image used in a ResourceLoaderImageModule.
+ *
+ * @since 1.25
+ */
+class ResourceLoaderImage {
+
+ /**
+ * Map of allowed file extensions to their MIME types.
+ * @var array
+ */
+ protected static $fileTypes = array(
+ 'svg' => 'image/svg+xml',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ 'jpg' => 'image/jpg',
+ );
+
+ /**
+ * @param string $name Image name
+ * @param string $module Module name
+ * @param string|array $descriptor Path to image file, or array structure containing paths
+ * @param string $basePath Directory to which paths in descriptor refer
+ * @param array $variants
+ * @throws InvalidArgumentException
+ */
+ public function __construct( $name, $module, $descriptor, $basePath, $variants ) {
+ $this->name = $name;
+ $this->module = $module;
+ $this->descriptor = $descriptor;
+ $this->basePath = $basePath;
+ $this->variants = $variants;
+
+ // Expand shorthands:
+ // array( "en,de,fr" => "foo.svg" ) → array( "en" => "foo.svg", "de" => "foo.svg", "fr" => "foo.svg" )
+ if ( is_array( $this->descriptor ) && isset( $this->descriptor['lang'] ) ) {
+ foreach ( array_keys( $this->descriptor['lang'] ) as $langList ) {
+ if ( strpos( $langList, ',' ) !== false ) {
+ $this->descriptor['lang'] += array_fill_keys(
+ explode( ',', $langList ),
+ $this->descriptor['lang'][ $langList ]
+ );
+ unset( $this->descriptor['lang'][ $langList ] );
+ }
+ }
+ }
+
+ // Ensure that all files have common extension.
+ $extensions = array();
+ $descriptor = (array)$descriptor;
+ array_walk_recursive( $descriptor, function ( $path ) use ( &$extensions ) {
+ $extensions[] = pathinfo( $path, PATHINFO_EXTENSION );
+ } );
+ $extensions = array_unique( $extensions );
+ if ( count( $extensions ) !== 1 ) {
+ throw new InvalidArgumentException( "File type for different image files of '$name' not the same" );
+ }
+ $ext = $extensions[0];
+ if ( !isset( self::$fileTypes[$ext] ) ) {
+ throw new InvalidArgumentException( "Invalid file type for image files of '$name' (valid: svg, png, gif, jpg)" );
+ }
+ $this->extension = $ext;
+ }
+
+ /**
+ * Get name of this image.
+ *
+ * @return string
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Get name of the module this image belongs to.
+ *
+ * @return string
+ */
+ public function getModule() {
+ return $this->module;
+ }
+
+ /**
+ * Get the list of variants this image can be converted to.
+ *
+ * @return string[]
+ */
+ public function getVariants() {
+ return array_keys( $this->variants );
+ }
+
+ /**
+ * Get the path to image file for given context.
+ *
+ * @param ResourceLoaderContext $context Any context
+ * @return string
+ */
+ protected function getPath( ResourceLoaderContext $context ) {
+ $desc = $this->descriptor;
+ if ( is_string( $desc ) ) {
+ return $this->basePath . '/' . $desc;
+ } elseif ( isset( $desc['lang'][ $context->getLanguage() ] ) ) {
+ return $this->basePath . '/' . $desc['lang'][ $context->getLanguage() ];
+ } elseif ( isset( $desc[ $context->getDirection() ] ) ) {
+ return $this->basePath . '/' . $desc[ $context->getDirection() ];
+ } else {
+ return $this->basePath . '/' . $desc['default'];
+ }
+ }
+
+ /**
+ * Get the extension of the image.
+ *
+ * @param string $format Format to get the extension for, 'original' or 'rasterized'
+ * @return string Extension without leading dot, e.g. 'png'
+ */
+ public function getExtension( $format = 'original' ) {
+ if ( $format === 'rasterized' && $this->extension === 'svg' ) {
+ return 'png';
+ } else {
+ return $this->extension;
+ }
+ }
+
+ /**
+ * Get the MIME type of the image.
+ *
+ * @param string $format Format to get the MIME type for, 'original' or 'rasterized'
+ * @return string
+ */
+ public function getMimeType( $format = 'original' ) {
+ $ext = $this->getExtension( $format );
+ return self::$fileTypes[$ext];
+ }
+
+ /**
+ * Get the load.php URL that will produce this image.
+ *
+ * @param ResourceLoaderContext $context Any context
+ * @param string $script URL to load.php
+ * @param string|null $variant Variant to get the URL for
+ * @param string $format Format to get the URL for, 'original' or 'rasterized'
+ * @return string
+ */
+ public function getUrl( ResourceLoaderContext $context, $script, $variant, $format ) {
+ $query = array(
+ 'modules' => $this->getModule(),
+ 'image' => $this->getName(),
+ 'variant' => $variant,
+ 'format' => $format,
+ 'lang' => $context->getLanguage(),
+ 'version' => $context->getVersion(),
+ );
+
+ return wfExpandUrl( wfAppendQuery( $script, $query ), PROTO_RELATIVE );
+ }
+
+ /**
+ * Get the data: URI that will produce this image.
+ *
+ * @param ResourceLoaderContext $context Any context
+ * @param string|null $variant Variant to get the URI for
+ * @param string $format Format to get the URI for, 'original' or 'rasterized'
+ * @return string
+ */
+ public function getDataUri( ResourceLoaderContext $context, $variant, $format ) {
+ $type = $this->getMimeType( $format );
+ $contents = $this->getImageData( $context, $variant, $format );
+ return CSSMin::encodeStringAsDataURI( $contents, $type );
+ }
+
+ /**
+ * Get actual image data for this image. This can be saved to a file or sent to the browser to
+ * produce the converted image.
+ *
+ * Call getExtension() or getMimeType() with the same $format argument to learn what file type the
+ * returned data uses.
+ *
+ * @param ResourceLoaderContext $context Image context, or any context if $variant and $format
+ * given.
+ * @param string|null $variant Variant to get the data for. Optional; if given, overrides the data
+ * from $context.
+ * @param string $format Format to get the data for, 'original' or 'rasterized'. Optional; if
+ * given, overrides the data from $context.
+ * @return string|false Possibly binary image data, or false on failure
+ * @throws MWException If the image file doesn't exist
+ */
+ public function getImageData( ResourceLoaderContext $context, $variant = false, $format = false ) {
+ if ( $variant === false ) {
+ $variant = $context->getVariant();
+ }
+ if ( $format === false ) {
+ $format = $context->getFormat();
+ }
+
+ $path = $this->getPath( $context );
+ if ( !file_exists( $path ) ) {
+ throw new MWException( "File '$path' does not exist" );
+ }
+
+ if ( $this->getExtension() !== 'svg' ) {
+ return file_get_contents( $path );
+ }
+
+ if ( $variant && isset( $this->variants[$variant] ) ) {
+ $data = $this->variantize( $this->variants[$variant], $context );
+ } else {
+ $data = file_get_contents( $path );
+ }
+
+ if ( $format === 'rasterized' ) {
+ $data = $this->rasterize( $data );
+ if ( !$data ) {
+ wfDebugLog( 'ResourceLoaderImage', __METHOD__ . " failed to rasterize for $path" );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Send response headers (using the header() function) that are necessary to correctly serve the
+ * image data for this image, as returned by getImageData().
+ *
+ * Note that the headers are independent of the language or image variant.
+ *
+ * @param ResourceLoaderContext $context Image context
+ */
+ public function sendResponseHeaders( ResourceLoaderContext $context ) {
+ $format = $context->getFormat();
+ $mime = $this->getMimeType( $format );
+ $filename = $this->getName() . '.' . $this->getExtension( $format );
+
+ header( 'Content-Type: ' . $mime );
+ header( 'Content-Disposition: ' .
+ FileBackend::makeContentDisposition( 'inline', $filename ) );
+ }
+
+ /**
+ * Convert this image, which is assumed to be SVG, to given variant.
+ *
+ * @param array $variantConf Array with a 'color' key, its value will be used as fill color
+ * @param ResourceLoaderContext $context Image context
+ * @return string New SVG file data
+ */
+ protected function variantize( $variantConf, ResourceLoaderContext $context ) {
+ $dom = new DomDocument;
+ $dom->load( $this->getPath( $context ) );
+ $root = $dom->documentElement;
+ $wrapper = $dom->createElement( 'g' );
+ while ( $root->firstChild ) {
+ $wrapper->appendChild( $root->firstChild );
+ }
+ $root->appendChild( $wrapper );
+ $wrapper->setAttribute( 'fill', $variantConf['color'] );
+ return $dom->saveXml();
+ }
+
+ /**
+ * Massage the SVG image data for converters which don't understand some path data syntax.
+ *
+ * This is necessary for rsvg and ImageMagick when compiled with rsvg support.
+ * Upstream bug is https://bugzilla.gnome.org/show_bug.cgi?id=620923, fixed 2014-11-10, so
+ * this will be needed for a while. (T76852)
+ *
+ * @param string $svg SVG image data
+ * @return string Massaged SVG image data
+ */
+ protected function massageSvgPathdata( $svg ) {
+ $dom = new DomDocument;
+ $dom->loadXml( $svg );
+ foreach ( $dom->getElementsByTagName( 'path' ) as $node ) {
+ $pathData = $node->getAttribute( 'd' );
+ // Make sure there is at least one space between numbers, and that leading zero is not omitted.
+ // rsvg has issues with syntax like "M-1-2" and "M.445.483" and especially "M-.445-.483".
+ $pathData = preg_replace( '/(-?)(\d*\.\d+|\d+)/', ' ${1}0$2 ', $pathData );
+ // Strip unnecessary leading zeroes for prettiness, not strictly necessary
+ $pathData = preg_replace( '/([ -])0(\d)/', '$1$2', $pathData );
+ $node->setAttribute( 'd', $pathData );
+ }
+ return $dom->saveXml();
+ }
+
+ /**
+ * Convert passed image data, which is assumed to be SVG, to PNG.
+ *
+ * @param string $svg SVG image data
+ * @return string|bool PNG image data, or false on failure
+ */
+ protected function rasterize( $svg ) {
+ // This code should be factored out to a separate method on SvgHandler, or perhaps a separate
+ // class, with a separate set of configuration settings.
+ //
+ // This is a distinct use case from regular SVG rasterization:
+ // * We can skip many sanity and security checks (as the images come from a trusted source,
+ // rather than from the user).
+ // * We need to provide extra options to some converters to achieve acceptable quality for very
+ // small images, which might cause performance issues in the general case.
+ // * We want to directly pass image data to the converter, rather than a file path.
+ //
+ // See https://phabricator.wikimedia.org/T76473#801446 for examples of what happens with the
+ // default settings.
+ //
+ // For now, we special-case rsvg (used in WMF production) and do a messy workaround for other
+ // converters.
+
+ global $wgSVGConverter, $wgSVGConverterPath;
+
+ $svg = $this->massageSvgPathdata( $svg );
+
+ // Sometimes this might be 'rsvg-secure'. Long as it's rsvg.
+ if ( strpos( $wgSVGConverter, 'rsvg' ) === 0 ) {
+ $command = 'rsvg-convert';
+ if ( $wgSVGConverterPath ) {
+ $command = wfEscapeShellArg( "$wgSVGConverterPath/" ) . $command;
+ }
+
+ $process = proc_open(
+ $command,
+ array( 0 => array( 'pipe', 'r' ), 1 => array( 'pipe', 'w' ) ),
+ $pipes
+ );
+
+ if ( is_resource( $process ) ) {
+ fwrite( $pipes[0], $svg );
+ fclose( $pipes[0] );
+ $png = stream_get_contents( $pipes[1] );
+ fclose( $pipes[1] );
+ proc_close( $process );
+
+ return $png ?: false;
+ }
+ return false;
+
+ } else {
+ // Write input to and read output from a temporary file
+ $tempFilenameSvg = tempnam( wfTempDir(), 'ResourceLoaderImage' );
+ $tempFilenamePng = tempnam( wfTempDir(), 'ResourceLoaderImage' );
+
+ file_put_contents( $tempFilenameSvg, $svg );
+
+ $metadata = SVGMetadataExtractor::getMetadata( $tempFilenameSvg );
+ if ( !isset( $metadata['width'] ) || !isset( $metadata['height'] ) ) {
+ unlink( $tempFilenameSvg );
+ return false;
+ }
+
+ $handler = new SvgHandler;
+ $res = $handler->rasterize(
+ $tempFilenameSvg,
+ $tempFilenamePng,
+ $metadata['width'],
+ $metadata['height']
+ );
+ unlink( $tempFilenameSvg );
+
+ $png = null;
+ if ( $res === true ) {
+ $png = file_get_contents( $tempFilenamePng );
+ unlink( $tempFilenamePng );
+ }
+
+ return $png ?: false;
+ }
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderImageModule.php b/includes/resourceloader/ResourceLoaderImageModule.php
new file mode 100644
index 00000000..bf6a7dd2
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderImageModule.php
@@ -0,0 +1,327 @@
+<?php
+/**
+ * Resource loader module for generated and embedded images.
+ *
+ * This 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 Trevor Parscal
+ */
+
+/**
+ * Resource loader module for generated and embedded images.
+ *
+ * @since 1.25
+ */
+class ResourceLoaderImageModule extends ResourceLoaderModule {
+
+ /**
+ * Local base path, see __construct()
+ * @var string
+ */
+ protected $localBasePath = '';
+
+ protected $origin = self::ORIGIN_CORE_SITEWIDE;
+
+ protected $images = array();
+ protected $variants = array();
+ protected $prefix = null;
+ protected $selectorWithoutVariant = '.{prefix}-{name}';
+ protected $selectorWithVariant = '.{prefix}-{name}-{variant}';
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /**
+ * Constructs a new module from an options array.
+ *
+ * @param array $options List of options; if not given or empty, an empty module will be
+ * constructed
+ * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults
+ * to $IP
+ *
+ * Below is a description for the $options array:
+ * @par Construction options:
+ * @code
+ * array(
+ * // Base path to prepend to all local paths in $options. Defaults to $IP
+ * 'localBasePath' => [base path],
+ * // CSS class prefix to use in all style rules
+ * 'prefix' => [CSS class prefix],
+ * // Alternatively: Format of CSS selector to use in all style rules
+ * 'selector' => [CSS selector template, variables: {prefix} {name} {variant}],
+ * // Alternatively: When using variants
+ * 'selectorWithoutVariant' => [CSS selector template, variables: {prefix} {name}],
+ * 'selectorWithVariant' => [CSS selector template, variables: {prefix} {name} {variant}],
+ * // List of variants that may be used for the image files
+ * 'variants' => array(
+ * [variant name] => array(
+ * 'color' => [color string, e.g. '#ffff00'],
+ * 'global' => [boolean, if true, this variant is available
+ * for all images of this type],
+ * ),
+ * ...
+ * ),
+ * // List of image files and their options
+ * 'images' => array(
+ * [file path string],
+ * [file path string] => array(
+ * 'name' => [image name string, defaults to file name],
+ * 'variants' => [array of variant name strings, variants
+ * available for this image],
+ * ),
+ * ...
+ * ),
+ * )
+ * @endcode
+ * @throws InvalidArgumentException
+ */
+ public function __construct( $options = array(), $localBasePath = null ) {
+ $this->localBasePath = self::extractLocalBasePath( $options, $localBasePath );
+
+ // Accepted combinations:
+ // * prefix
+ // * selector
+ // * selectorWithoutVariant + selectorWithVariant
+ // * prefix + selector
+ // * prefix + selectorWithoutVariant + selectorWithVariant
+
+ $prefix = isset( $options['prefix'] ) && $options['prefix'];
+ $selector = isset( $options['selector'] ) && $options['selector'];
+ $selectorWithoutVariant = isset( $options['selectorWithoutVariant'] ) && $options['selectorWithoutVariant'];
+ $selectorWithVariant = isset( $options['selectorWithVariant'] ) && $options['selectorWithVariant'];
+
+ if ( $selectorWithoutVariant && !$selectorWithVariant ) {
+ throw new InvalidArgumentException( "Given 'selectorWithoutVariant' but no 'selectorWithVariant'." );
+ }
+ if ( $selectorWithVariant && !$selectorWithoutVariant ) {
+ throw new InvalidArgumentException( "Given 'selectorWithVariant' but no 'selectorWithoutVariant'." );
+ }
+ if ( $selector && $selectorWithVariant ) {
+ throw new InvalidArgumentException( "Incompatible 'selector' and 'selectorWithVariant'+'selectorWithoutVariant' given." );
+ }
+ if ( !$prefix && !$selector && !$selectorWithVariant ) {
+ throw new InvalidArgumentException( "None of 'prefix', 'selector' or 'selectorWithVariant'+'selectorWithoutVariant' given." );
+ }
+
+ foreach ( $options as $member => $option ) {
+ switch ( $member ) {
+ case 'images':
+ case 'variants':
+ if ( !is_array( $option ) ) {
+ throw new InvalidArgumentException(
+ "Invalid list error. '$option' given, array expected."
+ );
+ }
+ $this->{$member} = $option;
+ break;
+
+ case 'prefix':
+ case 'selectorWithoutVariant':
+ case 'selectorWithVariant':
+ $this->{$member} = (string)$option;
+ break;
+
+ case 'selector':
+ $this->selectorWithoutVariant = $this->selectorWithVariant = (string)$option;
+ }
+ }
+ }
+
+ /**
+ * Get CSS class prefix used by this module.
+ * @return string
+ */
+ public function getPrefix() {
+ return $this->prefix;
+ }
+
+ /**
+ * Get CSS selector templates used by this module.
+ * @return string
+ */
+ public function getSelectors() {
+ return array(
+ 'selectorWithoutVariant' => $this->selectorWithoutVariant,
+ 'selectorWithVariant' => $this->selectorWithVariant,
+ );
+ }
+
+ /**
+ * Get a ResourceLoaderImage object for given image.
+ * @param string $name Image name
+ * @return ResourceLoaderImage|null
+ */
+ public function getImage( $name ) {
+ $images = $this->getImages();
+ return isset( $images[$name] ) ? $images[$name] : null;
+ }
+
+ /**
+ * Get ResourceLoaderImage objects for all images.
+ * @return ResourceLoaderImage[] Array keyed by image name
+ */
+ public function getImages() {
+ if ( !isset( $this->imageObjects ) ) {
+ $this->imageObjects = array();
+
+ foreach ( $this->images as $name => $options ) {
+ $fileDescriptor = is_string( $options ) ? $options : $options['file'];
+
+ $allowedVariants = array_merge(
+ is_array( $options ) && isset( $options['variants'] ) ? $options['variants'] : array(),
+ $this->getGlobalVariants()
+ );
+ if ( isset( $this->variants ) ) {
+ $variantConfig = array_intersect_key(
+ $this->variants,
+ array_fill_keys( $allowedVariants, true )
+ );
+ } else {
+ $variantConfig = array();
+ }
+
+ $image = new ResourceLoaderImage(
+ $name,
+ $this->getName(),
+ $fileDescriptor,
+ $this->localBasePath,
+ $variantConfig
+ );
+ $this->imageObjects[ $image->getName() ] = $image;
+ }
+ }
+
+ return $this->imageObjects;
+ }
+
+ /**
+ * Get list of variants in this module that are 'global', i.e., available
+ * for every image regardless of image options.
+ * @return string[]
+ */
+ public function getGlobalVariants() {
+ if ( !isset( $this->globalVariants ) ) {
+ $this->globalVariants = array();
+
+ if ( isset( $this->variants ) ) {
+ foreach ( $this->variants as $name => $config ) {
+ if ( isset( $config['global'] ) && $config['global'] ) {
+ $this->globalVariants[] = $name;
+ }
+ }
+ }
+ }
+
+ return $this->globalVariants;
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return array
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+ // Build CSS rules
+ $rules = array();
+ $script = $context->getResourceLoader()->getLoadScript( $this->getSource() );
+ $selectors = $this->getSelectors();
+
+ foreach ( $this->getImages() as $name => $image ) {
+ $declarations = $this->getCssDeclarations(
+ $image->getDataUri( $context, null, 'original' ),
+ $image->getUrl( $context, $script, null, 'rasterized' )
+ );
+ $declarations = implode( "\n\t", $declarations );
+ $selector = strtr(
+ $selectors['selectorWithoutVariant'],
+ array(
+ '{prefix}' => $this->getPrefix(),
+ '{name}' => $name,
+ '{variant}' => '',
+ )
+ );
+ $rules[] = "$selector {\n\t$declarations\n}";
+
+ foreach ( $image->getVariants() as $variant ) {
+ $declarations = $this->getCssDeclarations(
+ $image->getDataUri( $context, $variant, 'original' ),
+ $image->getUrl( $context, $script, $variant, 'rasterized' )
+ );
+ $declarations = implode( "\n\t", $declarations );
+ $selector = strtr(
+ $selectors['selectorWithVariant'],
+ array(
+ '{prefix}' => $this->getPrefix(),
+ '{name}' => $name,
+ '{variant}' => $variant,
+ )
+ );
+ $rules[] = "$selector {\n\t$declarations\n}";
+ }
+ }
+
+ $style = implode( "\n", $rules );
+ return array( 'all' => $style );
+ }
+
+ /**
+ * SVG support using a transparent gradient to guarantee cross-browser
+ * compatibility (browsers able to understand gradient syntax support also SVG).
+ * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique
+ *
+ * Keep synchronized with the .background-image-svg LESS mixin in
+ * /resources/src/mediawiki.less/mediawiki.mixins.less.
+ *
+ * @param string $primary Primary URI
+ * @param string $fallback Fallback URI
+ * @return string[] CSS declarations to use given URIs as background-image
+ */
+ protected function getCssDeclarations( $primary, $fallback ) {
+ return array(
+ "background-image: url($fallback);",
+ "background-image: -webkit-linear-gradient(transparent, transparent), url($primary);",
+ "background-image: linear-gradient(transparent, transparent), url($primary);",
+ "background-image: -o-linear-gradient(transparent, transparent), url($fallback);",
+ );
+ }
+
+ /**
+ * @return bool
+ */
+ public function supportsURLLoading() {
+ return false;
+ }
+
+ /**
+ * Extract a local base path from module definition information.
+ *
+ * @param array $options Module definition
+ * @param string $localBasePath Path to use if not provided in module definition. Defaults
+ * to $IP
+ * @return string Local base path
+ */
+ public static function extractLocalBasePath( $options, $localBasePath = null ) {
+ global $IP;
+
+ if ( $localBasePath === null ) {
+ $localBasePath = $IP;
+ }
+
+ if ( array_key_exists( 'localBasePath', $options ) ) {
+ $localBasePath = (string)$options['localBasePath'];
+ }
+
+ return $localBasePath;
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
index 09d90d6e..12394536 100644
--- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php
@@ -52,10 +52,14 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule {
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall( 'mw.language.setData', array(
- $context->getLanguage(),
- $this->getData( $context )
- ) );
+ return Xml::encodeJsCall(
+ 'mw.language.setData',
+ array(
+ $context->getLanguage(),
+ $this->getData( $context )
+ ),
+ ResourceLoader::inDebugMode()
+ );
}
/**
diff --git a/includes/resourceloader/ResourceLoaderLanguageNamesModule.php b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
index fe0c8454..55b1f4b1 100644
--- a/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
+++ b/includes/resourceloader/ResourceLoaderLanguageNamesModule.php
@@ -49,11 +49,15 @@ class ResourceLoaderLanguageNamesModule extends ResourceLoaderModule {
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall( 'mw.language.setData', array(
- $context->getLanguage(),
- 'languageNames',
- $this->getData( $context )
- ) );
+ return Xml::encodeJsCall(
+ 'mw.language.setData',
+ array(
+ $context->getLanguage(),
+ 'languageNames',
+ $this->getData( $context )
+ ),
+ ResourceLoader::inDebugMode()
+ );
}
public function getDependencies() {
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 45eb70f8..ed16521b 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -135,6 +135,16 @@ abstract class ResourceLoaderModule {
}
/**
+ * Takes named templates by the module and returns an array mapping.
+ *
+ * @return array of templates mapping template alias to content
+ */
+ public function getTemplates() {
+ // Stub, override expected.
+ return array();
+ }
+
+ /**
* @return Config
* @since 1.24
*/
@@ -378,12 +388,12 @@ 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
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
if ( !count( $this->getMessages() ) ) {
- return 0;
+ return 1;
}
$dbr = wfGetDB( DB_SLAVE );
@@ -406,7 +416,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 int $mtime UNIX timestamp or 0 if there is no such blob
+ * @param int $mtime UNIX timestamp
*/
public function setMsgBlobMtime( $lang, $mtime ) {
$this->msgBlobMtime[$lang] = $mtime;
@@ -433,7 +443,6 @@ abstract class ResourceLoaderModule {
* @return int UNIX timestamp
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- // 0 would mean now
return 1;
}
@@ -441,30 +450,34 @@ abstract class ResourceLoaderModule {
* Helper method for calculating when the module's hash (if it has one) changed.
*
* @param ResourceLoaderContext $context
- * @return int UNIX timestamp or 0 if no hash was provided
- * by getModifiedHash()
+ * @return int UNIX timestamp
*/
public function getHashMtime( ResourceLoaderContext $context ) {
$hash = $this->getModifiedHash( $context );
if ( !is_string( $hash ) ) {
- return 0;
+ return 1;
}
+ // 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.
$cache = wfGetCache( CACHE_ANYTHING );
- $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName(), $hash );
+ $key = wfMemcKey( 'resourceloader', 'hashmtime', $this->getName(), $hash );
$data = $cache->get( $key );
- if ( is_array( $data ) && $data['hash'] === $hash ) {
- // Hash is still the same, re-use the timestamp of when we first saw this hash.
- return $data['timestamp'];
+ if ( is_int( $data ) && $data > 0 ) {
+ // We've seen this hash before, re-use the timestamp of when we first saw it.
+ return $data;
}
- $timestamp = wfTimestamp();
- $cache->set( $key, array(
- 'hash' => $hash,
- 'timestamp' => $timestamp,
- ) );
-
+ $timestamp = time();
+ $cache->set( $key, $timestamp );
return $timestamp;
}
@@ -487,46 +500,29 @@ abstract class ResourceLoaderModule {
* @since 1.23
*
* @param ResourceLoaderContext $context
- * @return int UNIX timestamp or 0 if no definition summary was provided
- * by getDefinitionSummary()
+ * @return int UNIX timestamp
*/
public function getDefinitionMtime( ResourceLoaderContext $context ) {
- wfProfileIn( __METHOD__ );
$summary = $this->getDefinitionSummary( $context );
if ( $summary === null ) {
- wfProfileOut( __METHOD__ );
- return 0;
+ return 1;
}
$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." );
+ wfDebugLog( 'resourceloader', __METHOD__ . ": New definition for module "
+ . "{$this->getName()} in context \"{$context->getHash()}\"" );
$timestamp = time();
$cache->set( $key, $timestamp );
-
- wfProfileOut( __METHOD__ );
return $timestamp;
}
@@ -630,16 +626,13 @@ abstract class ResourceLoaderModule {
* Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist
* but returns 1 instead.
* @param string $filename File name
- * @return int UNIX timestamp, or 1 if the file doesn't exist
+ * @return int UNIX timestamp
*/
protected static function safeFilemtime( $filename ) {
- if ( file_exists( $filename ) ) {
- return filemtime( $filename );
- } else {
- // We only ever map this function on an array if we're gonna call max() after,
- // so return our standard minimum timestamps here. This is 1, not 0, because
- // wfTimestamp(0) == NOW
- return 1;
- }
+ wfSuppressWarnings();
+ $mtime = filemtime( $filename ) ?: 1;
+ wfRestoreWarnings();
+
+ return $mtime;
}
}
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 1d9721aa..19e0baeb 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -27,13 +27,10 @@
*/
class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
- /* Protected Methods */
-
/**
- * Gets list of pages used by this module
+ * Get list of pages used by this module
*
* @param ResourceLoaderContext $context
- *
* @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
@@ -45,18 +42,16 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
if ( $this->getConfig()->get( 'UseSiteCss' ) ) {
$pages['MediaWiki:Common.css'] = array( 'type' => 'style' );
$pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.css'] = array( 'type' => 'style' );
+ $pages['MediaWiki:Print.css'] = array( 'type' => 'style', 'media' => 'print' );
}
- $pages['MediaWiki:Print.css'] = array( 'type' => 'style', 'media' => 'print' );
return $pages;
}
- /* Methods */
-
/**
- * Gets group name
+ * Get group name
*
- * @return string Name of group
+ * @return string
*/
public function getGroup() {
return 'site';
diff --git a/includes/resourceloader/ResourceLoaderSkinModule.php b/includes/resourceloader/ResourceLoaderSkinModule.php
new file mode 100644
index 00000000..3ba63e68
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderSkinModule.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Resource loader module for skin stylesheets.
+ *
+ * This 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 Timo Tijhof
+ */
+
+class ResourceLoaderSkinModule extends ResourceLoaderFileModule {
+
+ /* Methods */
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+ $logo = $this->getConfig()->get( 'Logo' );
+ $logoHD = $this->getConfig()->get( 'LogoHD' );
+ $styles = parent::getStyles( $context );
+ $styles['all'][] = '.mw-wiki-logo { background-image: ' .
+ CSSMin::buildUrlValue( $logo ) .
+ '; }';
+ if ( $logoHD ) {
+ if ( isset( $logoHD['1.5x'] ) ) {
+ $styles[
+ '(-webkit-min-device-pixel-ratio: 1.5), ' .
+ '(min--moz-device-pixel-ratio: 1.5), ' .
+ '(min-resolution: 1.5dppx), ' .
+ '(min-resolution: 144dpi)'
+ ][] = '.mw-wiki-logo { background-image: ' .
+ CSSMin::buildUrlValue( $logoHD['1.5x'] ) .';' .
+ 'background-size: 135px auto; }';
+ }
+ if ( isset( $logoHD['2x'] ) ) {
+ $styles[
+ '(-webkit-min-device-pixel-ratio: 2), ' .
+ '(min--moz-device-pixel-ratio: 2),'.
+ '(min-resolution: 2dppx), ' .
+ '(min-resolution: 192dpi)'
+ ][] = '.mw-wiki-logo { background-image: ' .
+ CSSMin::buildUrlValue( $logoHD['2x'] ) . ';' .
+ 'background-size: 135px auto; }';
+ }
+ }
+ return $styles;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return bool
+ */
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ // Regardless of whether the files are specified, we always
+ // provide mw-wiki-logo styles.
+ return false;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return int|mixed
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ $parentMTime = parent::getModifiedTime( $context );
+ return max( $parentMTime, $this->getHashMtime( $context ) );
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string: Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ $logo = $this->getConfig()->get( 'Logo' );
+ $logoHD = $this->getConfig()->get( 'LogoHD' );
+ return md5( parent::getModifiedHash( $context ) . $logo . json_encode( $logoHD ) );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php b/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php
new file mode 100644
index 00000000..5c917091
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Resource loader module for populating special characters data for some
+ * editing extensions to use.
+ *
+ * This 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
+ */
+
+/**
+ * Resource loader module for populating special characters data for some
+ * editing extensions to use.
+ */
+class ResourceLoaderSpecialCharacterDataModule extends ResourceLoaderModule {
+ private $path = "resources/src/mediawiki.language/specialcharacters.json";
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /**
+ * Get all the dynamic data.
+ *
+ * @return array
+ */
+ protected function getData() {
+ return json_decode( file_get_contents( $this->path ) );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string JavaScript code
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall(
+ 'mw.language.setSpecialCharacters',
+ array(
+ $this->getData()
+ ),
+ ResourceLoader::inDebugMode()
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return static::safeFilemtime( $this->path );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5( serialize( $this->getData() ) );
+ }
+
+ /**
+ * @return array
+ */
+ public function getDependencies() {
+ return array( 'mediawiki.language' );
+ }
+
+ /**
+ * @return array
+ */
+ public function getMessages() {
+ return array(
+ 'special-characters-group-latin',
+ 'special-characters-group-latinextended',
+ 'special-characters-group-ipa',
+ 'special-characters-group-symbols',
+ 'special-characters-group-greek',
+ 'special-characters-group-cyrillic',
+ 'special-characters-group-arabic',
+ 'special-characters-group-arabicextended',
+ 'special-characters-group-persian',
+ 'special-characters-group-hebrew',
+ 'special-characters-group-bangla',
+ 'special-characters-group-tamil',
+ 'special-characters-group-telugu',
+ 'special-characters-group-sinhala',
+ 'special-characters-group-devanagari',
+ 'special-characters-group-gujarati',
+ 'special-characters-group-thai',
+ 'special-characters-group-lao',
+ 'special-characters-group-khmer',
+ 'special-characters-title-endash',
+ 'special-characters-title-emdash',
+ 'special-characters-title-minus'
+ );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 78fe8e01..b2fbae9c 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -82,6 +82,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgServerName' => $conf->get( 'ServerName' ),
'wgUserLanguage' => $context->getLanguage(),
'wgContentLanguage' => $wgContLang->getCode(),
+ 'wgTranslateNumerals' => $conf->get( 'TranslateNumerals' ),
'wgVersion' => $conf->get( 'Version' ),
'wgEnableAPI' => $conf->get( 'EnableAPI' ),
'wgEnableWriteAPI' => $conf->get( 'EnableWriteAPI' ),
@@ -90,11 +91,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgNamespaceIds' => $namespaceIds,
'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' => SpecialUpload::rotationEnabled(),
'wgAvailableSkins' => Skin::getSkinNames(),
'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
// MediaWiki sets cookies to have this prefix by default
@@ -109,7 +106,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
);
- wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
+ Hooks::run( 'ResourceLoaderGetConfigVars', array( &$vars ) );
$this->configVars[$hash] = $vars;
return $this->configVars[$hash];
@@ -150,7 +147,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * Optimize the dependency tree in $this->modules and return it.
+ * Optimize the dependency tree in $this->modules.
*
* The optimization basically works like this:
* Given we have module A with the dependencies B and C
@@ -158,11 +155,11 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
* 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
+ * This way we can reasonably reduce the amount of module registration
* data send to the client.
*
* @param array &$registryData Modules keyed by name with properties:
- * - string 'version'
+ * - number 'version'
* - array 'dependencies'
* - string|null 'group'
* - string 'source'
@@ -191,7 +188,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
* @return string JavaScript code for registering all modules with the client loader
*/
public function getModuleRegistrations( ResourceLoaderContext $context ) {
- wfProfileIn( __METHOD__ );
$resourceLoader = $context->getResourceLoader();
$target = $context->getRequest()->getVal( 'target', 'desktop' );
@@ -214,12 +210,10 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
continue;
}
- // 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
+ // Coerce module timestamp to UNIX timestamp.
+ // getModifiedTime() is supposed to return a UNIX timestamp, but custom implementations
+ // might forget. TODO: Maybe emit warning?
$moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
- $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() ) {
@@ -232,8 +226,14 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
);
}
+ $mtime = max(
+ $moduleMtime,
+ wfTimestamp( TS_UNIX, $this->getConfig()->get( 'CacheEpoch' ) )
+ );
+
$registryData[$name] = array(
- 'version' => $mtime,
+ // Convert to numbers as wfTimestamp always returns a string, even for TS_UNIX
+ 'version' => (int) $mtime,
'dependencies' => $module->getDependencies(),
'group' => $module->getGroup(),
'source' => $module->getSource(),
@@ -254,7 +254,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
if ( $data['loader'] !== false ) {
$out .= ResourceLoader::makeCustomLoaderScript(
$name,
- wfTimestamp( TS_ISO_8601_BASIC, $data['version'] ),
+ $data['version'],
$data['dependencies'],
$data['group'],
$data['source'],
@@ -263,63 +263,21 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
continue;
}
- 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']
- );
- }
+ // Call mw.loader.register(name, timestamp, dependencies, group, source, skip)
+ $registrations[] = array(
+ $name,
+ $data['version'],
+ $data['dependencies'],
+ $data['group'],
+ // Swap default (local) for null
+ $data['source'] === 'local' ? null : $data['source'],
+ $data['skip']
+ );
}
// Register modules
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
- wfProfileOut( __METHOD__ );
return $out;
}
@@ -333,7 +291,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
/**
- * Base modules required for the the base environment of ResourceLoader
+ * Base modules required for the base environment of ResourceLoader
*
* @return array
*/
@@ -355,7 +313,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// Get the latest version
$loader = $context->getResourceLoader();
- $version = 0;
+ $version = 1;
foreach ( $moduleNames as $moduleName ) {
$version = max( $version,
$loader->getModule( $moduleName )->getModifiedTime( $context )
@@ -390,18 +348,28 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$registrations = $this->getModuleRegistrations( $context );
// Fix indentation
$registrations = str_replace( "\n", "\n\t", trim( $registrations ) );
+ $mwMapJsCall = Xml::encodeJsCall(
+ 'mw.Map',
+ array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) )
+ );
+ $mwConfigSetJsCall = Xml::encodeJsCall(
+ 'mw.config.set',
+ array( $configuration ),
+ ResourceLoader::inDebugMode()
+ );
+
$out .= "var startUp = function () {\n" .
"\tmw.config = new " .
- Xml::encodeJsCall( 'mw.Map', array( $this->getConfig()->get( 'LegacyJavaScriptGlobals' ) ) ) . "\n" .
+ $mwMapJsCall . "\n" .
"\t$registrations\n" .
- "\t" . Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ) .
+ "\t" . $mwConfigSetJsCall .
"};\n";
// Conditional script injection
$scriptTag = Html::linkedScript( self::getStartupModulesUrl( $context ) );
$out .= "if ( isCompatible() ) {\n" .
"\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
- "}";
+ "\n}";
}
return $out;
@@ -440,8 +408,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// ATTENTION!: Because of the line below, this is not going to cause
// infinite recursion - think carefully before making changes to this
// code!
- // Pre-populate modifiedTime with something because the the loop over
- // all modules below includes the the startup module (this module).
+ // Pre-populate modifiedTime with something because the loop over
+ // all modules below includes the startup module (this module).
$this->modifiedTime[$hash] = 1;
foreach ( $loader->getModuleNames() as $name ) {
diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
index 40274c63..472ceb26 100644
--- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
@@ -42,8 +42,7 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
if ( !isset( $this->modifiedTime[$hash] ) ) {
- global $wgUser;
- $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $context->getUserObj()->getTouched() );
}
return $this->modifiedTime[$hash];
@@ -54,13 +53,11 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
* @return array
*/
public function getStyles( ResourceLoaderContext $context ) {
- global $wgUser;
-
if ( !$this->getConfig()->get( 'AllowUserCssPrefs' ) ) {
return array();
}
- $options = $wgUser->getOptions();
+ $options = $context->getUserObj()->getOptions();
// Build CSS rules
$rules = array();
@@ -93,11 +90,4 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
public function getGroup() {
return 'private';
}
-
- /**
- * @return array
- */
- public function getDependencies() {
- return array( 'mediawiki.user' );
- }
}
diff --git a/includes/resourceloader/ResourceLoaderUserDefaultsModule.php b/includes/resourceloader/ResourceLoaderUserDefaultsModule.php
new file mode 100644
index 00000000..5f4bc16b
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserDefaultsModule.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Resource loader module for default user preferences.
+ *
+ * This 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 Ori Livneh
+ */
+
+/**
+ * Module for default user preferences.
+ */
+class ResourceLoaderUserDefaultsModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ protected $targets = array( 'desktop', 'mobile' );
+
+ /* Methods */
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5( serialize( User::getDefaultOptions() ) );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return $this->getHashMtime( $context );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall(
+ 'mw.user.options.set',
+ array( User::getDefaultOptions() ),
+ ResourceLoader::inDebugMode()
+ );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
index 7cf19420..417cfced 100644
--- a/includes/resourceloader/ResourceLoaderUserGroupsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
@@ -25,39 +25,23 @@
*/
class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
- /* Protected Members */
-
protected $origin = self::ORIGIN_USER_SITEWIDE;
protected $targets = array( 'desktop', 'mobile' );
- /* Protected Methods */
-
/**
* @param ResourceLoaderContext $context
* @return array
*/
protected function getPages( ResourceLoaderContext $context ) {
- global $wgUser;
-
- $userName = $context->getUser();
- if ( $userName === null ) {
- return array();
- }
-
$useSiteJs = $this->getConfig()->get( 'UseSiteJs' );
$useSiteCss = $this->getConfig()->get( 'UseSiteCss' );
if ( !$useSiteJs && !$useSiteCss ) {
return array();
}
- // Use $wgUser is possible; allows to skip a lot of code
- if ( is_object( $wgUser ) && $wgUser->getName() == $userName ) {
- $user = $wgUser;
- } else {
- $user = User::newFromName( $userName );
- if ( !$user instanceof User ) {
- return array();
- }
+ $user = $context->getUserObj();
+ if ( !$user || $user->isAnon() ) {
+ return array();
}
$pages = array();
@@ -75,9 +59,9 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
return $pages;
}
- /* Methods */
-
/**
+ * Get group name
+ *
* @return string
*/
public function getGroup() {
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index 1b6d1de0..a0978445 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -27,47 +27,37 @@
*/
class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
- /* Protected Members */
-
protected $origin = self::ORIGIN_USER_INDIVIDUAL;
- /* Protected Methods */
-
/**
+ * Get list of pages used by this module
+ *
* @param ResourceLoaderContext $context
- * @return array
+ * @return array List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
- $username = $context->getUser();
-
- if ( $username === null ) {
- return array();
- }
-
$allowUserJs = $this->getConfig()->get( 'AllowUserJs' );
$allowUserCss = $this->getConfig()->get( 'AllowUserCss' );
-
if ( !$allowUserJs && !$allowUserCss ) {
return array();
}
- // Get the normalized title of the user's user page
- $userpageTitle = Title::makeTitleSafe( NS_USER, $username );
-
- if ( !$userpageTitle instanceof Title ) {
+ $user = $context->getUserObj();
+ if ( !$user || $user->isAnon() ) {
return array();
}
- $userpage = $userpageTitle->getPrefixedDBkey(); // Needed so $excludepages works
+ // Needed so $excludepages works
+ $userPage = $user->getUserPage()->getPrefixedDBkey();
$pages = array();
if ( $allowUserJs ) {
- $pages["$userpage/common.js"] = array( 'type' => 'script' );
- $pages["$userpage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' );
+ $pages["$userPage/common.js"] = array( 'type' => 'script' );
+ $pages["$userPage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' );
}
if ( $allowUserCss ) {
- $pages["$userpage/common.css"] = array( 'type' => 'style' );
- $pages["$userpage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' );
+ $pages["$userPage/common.css"] = array( 'type' => 'style' );
+ $pages["$userPage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' );
}
// Hack for bug 26283: if we're on a preview page for a CSS/JS page,
@@ -82,9 +72,9 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
return $pages;
}
- /* Methods */
-
/**
+ * Get group name
+ *
* @return string
*/
public function getGroup() {
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index bd97a8e5..84c1906d 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -38,14 +38,20 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
/* Methods */
/**
+ * @return array List of module names as strings
+ */
+ public function getDependencies() {
+ return array( 'user.defaults' );
+ }
+
+ /**
* @param ResourceLoaderContext $context
- * @return array|int|mixed
+ * @return int
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
if ( !isset( $this->modifiedTime[$hash] ) ) {
- global $wgUser;
- $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $context->getUserObj()->getTouched() );
}
return $this->modifiedTime[$hash];
@@ -56,9 +62,8 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
- global $wgUser;
return Xml::encodeJsCall( 'mw.user.options.set',
- array( $wgUser->getOptions() ),
+ array( $context->getUserObj()->getOptions( User::GETOPTIONS_EXCLUDE_DEFAULTS ) ),
ResourceLoader::inDebugMode()
);
}
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
index 668467ca..ccd1dfd0 100644
--- a/includes/resourceloader/ResourceLoaderUserTokensModule.php
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -37,15 +37,16 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
/**
* Fetch the tokens for the current user.
*
+ * @param ResourceLoaderContext $context
* @return array List of tokens keyed by token type
*/
- protected function contextUserTokens() {
- global $wgUser;
+ protected function contextUserTokens( ResourceLoaderContext $context ) {
+ $user = $context->getUserObj();
return array(
- 'editToken' => $wgUser->getEditToken(),
- 'patrolToken' => $wgUser->getEditToken( 'patrol' ),
- 'watchToken' => $wgUser->getEditToken( 'watch' ),
+ 'editToken' => $user->getEditToken(),
+ 'patrolToken' => $user->getEditToken( 'patrol' ),
+ 'watchToken' => $user->getEditToken( 'watch' ),
);
}
@@ -55,7 +56,7 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
*/
public function getScript( ResourceLoaderContext $context ) {
return Xml::encodeJsCall( 'mw.user.tokens.set',
- array( $this->contextUserTokens() ),
+ array( $this->contextUserTokens( $context ) ),
ResourceLoader::inDebugMode()
);
}
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index de61fc55..7b44cc67 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -29,17 +29,37 @@
* because of its dependence on the functionality of
* Title::isCssJsSubpage.
*/
-abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
+class ResourceLoaderWikiModule extends ResourceLoaderModule {
- /* Protected Members */
-
- # Origin is user-supplied code
+ // Origin defaults to users with sitewide authority
protected $origin = self::ORIGIN_USER_SITEWIDE;
// In-object cache for title info
protected $titleInfo = array();
- /* Abstract Protected Methods */
+ // List of page names that contain CSS
+ protected $styles = array();
+
+ // List of page names that contain JavaScript
+ protected $scripts = array();
+
+ // Group of module
+ protected $group;
+
+ /**
+ * @param array $options For back-compat, this can be omitted in favour of overwriting getPages.
+ */
+ public function __construct( array $options = null ) {
+ if ( isset( $options['styles'] ) ) {
+ $this->styles = $options['styles'];
+ }
+ if ( isset( $options['scripts'] ) ) {
+ $this->scripts = $options['scripts'];
+ }
+ if ( isset( $options['group'] ) ) {
+ $this->group = $options['group'];
+ }
+ }
/**
* Subclasses should return an associative array of resources in the module.
@@ -57,9 +77,34 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* @param ResourceLoaderContext $context
* @return array
*/
- abstract protected function getPages( ResourceLoaderContext $context );
+ protected function getPages( ResourceLoaderContext $context ) {
+ $config = $this->getConfig();
+ $pages = array();
- /* Protected Methods */
+ // Filter out pages from origins not allowed by the current wiki configuration.
+ if ( $config->get( 'UseSiteJs' ) ) {
+ foreach ( $this->scripts as $script ) {
+ $pages[$script] = array( 'type' => 'script' );
+ }
+ }
+
+ if ( $config->get( 'UseSiteCss' ) ) {
+ foreach ( $this->styles as $style ) {
+ $pages[$style] = array( 'type' => 'style' );
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Get group name
+ *
+ * @return string
+ */
+ public function getGroup() {
+ return $this->group;
+ }
/**
* Get the Database object used in getTitleMTimes(). Defaults to the local slave DB
@@ -70,7 +115,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* In particular, it doesn't work for getting the content of JS and CSS pages. That functionality
* will use the local DB irrespective of the return value of this method.
*
- * @return DatabaseBase|null
+ * @return IDatabase|null
*/
protected function getDB() {
return wfGetDB( DB_SLAVE );
@@ -81,9 +126,15 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* @return null|string
*/
protected function getContent( $title ) {
- if ( !$title->isCssJsSubpage() && !$title->isCssOrJsPage() ) {
+ $handler = ContentHandler::getForTitle( $title );
+ if ( $handler->isSupportedFormat( CONTENT_FORMAT_CSS ) ) {
+ $format = CONTENT_FORMAT_CSS;
+ } elseif ( $handler->isSupportedFormat( CONTENT_FORMAT_JAVASCRIPT ) ) {
+ $format = CONTENT_FORMAT_JAVASCRIPT;
+ } else {
return null;
}
+
$revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
if ( !$revision ) {
return null;
@@ -96,18 +147,9 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return null;
}
- 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->serialize( $format );
}
- /* Methods */
-
/**
* @param ResourceLoaderContext $context
* @return string
@@ -165,13 +207,13 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
/**
* @param ResourceLoaderContext $context
- * @return int|mixed
+ * @return int
*/
public function getModifiedTime( ResourceLoaderContext $context ) {
- $modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
+ $modifiedTime = 1;
$titleInfo = $this->getTitleInfo( $context );
if ( count( $titleInfo ) ) {
- $mtimes = array_map( function( $value ) {
+ $mtimes = array_map( function ( $value ) {
return $value['timestamp'];
}, $titleInfo );
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
@@ -227,8 +269,8 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* Get the modification times of all titles that would be loaded for
* a given context.
* @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
+ * @return array Keyed by page dbkey. Value is an array with 'length' and 'timestamp'
+ * keys, where the timestamp is a UNIX timestamp
*/
protected function getTitleInfo( ResourceLoaderContext $context ) {
$dbr = $this->getDB();
diff --git a/includes/revisiondelete/RevDelArchiveList.php b/includes/revisiondelete/RevDelArchiveList.php
index e7aed737..6ae0afce 100644
--- a/includes/revisiondelete/RevDelArchiveList.php
+++ b/includes/revisiondelete/RevDelArchiveList.php
@@ -32,7 +32,7 @@ class RevDelArchiveList extends RevDelRevisionList {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
diff --git a/includes/revisiondelete/RevDelArchivedFileList.php b/includes/revisiondelete/RevDelArchivedFileList.php
index aec51b17..f2b99aed 100644
--- a/includes/revisiondelete/RevDelArchivedFileList.php
+++ b/includes/revisiondelete/RevDelArchivedFileList.php
@@ -32,7 +32,7 @@ class RevDelArchivedFileList extends RevDelFileList {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
diff --git a/includes/revisiondelete/RevDelFileList.php b/includes/revisiondelete/RevDelFileList.php
index 57e15d81..2295eaa1 100644
--- a/includes/revisiondelete/RevDelFileList.php
+++ b/includes/revisiondelete/RevDelFileList.php
@@ -49,7 +49,7 @@ class RevDelFileList extends RevDelList {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
diff --git a/includes/revisiondelete/RevDelList.php b/includes/revisiondelete/RevDelList.php
index a0ff6672..840fd772 100644
--- a/includes/revisiondelete/RevDelList.php
+++ b/includes/revisiondelete/RevDelList.php
@@ -247,16 +247,22 @@ abstract class RevDelList extends RevisionListBase {
} else {
$logType = 'delete';
}
- // Add params for effected page and ids
+ // Add params for affected page and ids
$logParams = $this->getLogParams( $params );
// Actually add the deletion log entry
- $log = new LogPage( $logType );
- $logid = $log->addEntry( $this->getLogAction(), $params['title'],
- $params['comment'], $logParams, $this->getUser() );
+ $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
+ $logEntry->setTarget( $params['title'] );
+ $logEntry->setComment( $params['comment'] );
+ $logEntry->setParameters( $logParams );
+ $logEntry->setPerformer( $this->getUser() );
// Allow for easy searching of deletion log items for revision/log items
- $log->addRelations( $field, $params['ids'], $logid );
- $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
- $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
+ $logEntry->setRelations( array(
+ $field => $params['ids'],
+ 'target_author_id' => $params['authorIds'],
+ 'target_author_ip' => $params['authorIPs'],
+ ) );
+ $logId = $logEntry->insert();
+ $logEntry->publish( $logId );
}
/**
@@ -274,10 +280,10 @@ abstract class RevDelList extends RevisionListBase {
*/
public function getLogParams( $params ) {
return array(
- $this->getType(),
- implode( ',', $params['ids'] ),
- "ofield={$params['oldBits']}",
- "nfield={$params['newBits']}"
+ '4::type' => $this->getType(),
+ '5::ids' => $params['ids'],
+ '6::ofield' => $params['oldBits'],
+ '7::nfield' => $params['newBits'],
);
}
diff --git a/includes/revisiondelete/RevDelLogItem.php b/includes/revisiondelete/RevDelLogItem.php
index 5c8b8c9d..49adf204 100644
--- a/includes/revisiondelete/RevDelLogItem.php
+++ b/includes/revisiondelete/RevDelLogItem.php
@@ -124,15 +124,7 @@ class RevDelLogItem extends RevDelItem {
: array();
if ( LogEventsList::userCan( $this->row, LogPage::DELETED_ACTION, $user ) ) {
- ApiQueryLogEvents::addLogParams(
- $result,
- $ret,
- $logEntry->getParameters(),
- $logEntry->getType(),
- $logEntry->getSubtype(),
- $logEntry->getTimestamp(),
- $logEntry->isLegacy()
- );
+ $ret['params'] = LogFormatter::newFromEntry( $logEntry )->formatParametersForApi();
}
if ( LogEventsList::userCan( $this->row, LogPage::DELETED_USER, $user ) ) {
$ret += array(
diff --git a/includes/revisiondelete/RevDelLogList.php b/includes/revisiondelete/RevDelLogList.php
index ad040425..d8bd2f7e 100644
--- a/includes/revisiondelete/RevDelLogList.php
+++ b/includes/revisiondelete/RevDelLogList.php
@@ -55,7 +55,7 @@ class RevDelLogList extends RevDelList {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
@@ -95,9 +95,9 @@ class RevDelLogList extends RevDelList {
public function getLogParams( $params ) {
return array(
- implode( ',', $params['ids'] ),
- "ofield={$params['oldBits']}",
- "nfield={$params['newBits']}"
+ '4::ids' => $params['ids'],
+ '5::ofield' => $params['oldBits'],
+ '6::nfield' => $params['newBits'],
);
}
}
diff --git a/includes/revisiondelete/RevDelRevisionList.php b/includes/revisiondelete/RevDelRevisionList.php
index 25450725..4a0fff87 100644
--- a/includes/revisiondelete/RevDelRevisionList.php
+++ b/includes/revisiondelete/RevDelRevisionList.php
@@ -54,7 +54,7 @@ class RevDelRevisionList extends RevDelList {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
@@ -137,7 +137,7 @@ class RevDelRevisionList extends RevDelList {
public function doPostCommitUpdates() {
$this->title->purgeSquid();
// Extensions that require referencing previous revisions may need this
- wfRunHooks( 'ArticleRevisionVisibilitySet', array( &$this->title ) );
+ Hooks::run( 'ArticleRevisionVisibilitySet', array( $this->title, $this->ids ) );
return Status::newGood();
}
}
diff --git a/includes/revisiondelete/RevisionDeleteUser.php b/includes/revisiondelete/RevisionDeleteUser.php
index 55c46c5e..79802d66 100644
--- a/includes/revisiondelete/RevisionDeleteUser.php
+++ b/includes/revisiondelete/RevisionDeleteUser.php
@@ -36,14 +36,14 @@ class RevisionDeleteUser {
* @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
+ * @param null|IDatabase $dbw If you happen to have one lying around
* @return bool
*/
private static function setUsernameBitfields( $name, $userId, $op, $dbw ) {
if ( !$userId || ( $op !== '|' && $op !== '&' ) ) {
return false; // sanity check
}
- if ( !$dbw instanceof DatabaseBase ) {
+ if ( !$dbw instanceof IDatabase ) {
$dbw = wfGetDB( DB_MASTER );
}
diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php
index d4f81678..ba1f0f69 100644
--- a/includes/revisiondelete/RevisionDeleter.php
+++ b/includes/revisiondelete/RevisionDeleter.php
@@ -78,6 +78,7 @@ class RevisionDeleter {
* @param Title $title
* @param array $ids
* @return RevDelList
+ * @throws MWException
*/
public static function createList( $typeName, IContextSource $context, Title $title, array $ids ) {
$typeName = self::getCanonicalTypeName( $typeName );
@@ -115,7 +116,7 @@ class RevisionDeleter {
* "revdelete-restricted", "revdelete-unrestricted" indicating (un)suppression
* or null to indicate nothing in particular.
* You can turn the keys in $arr[0] and $arr[1] into message keys by
- * appending -hid and and -unhid to the keys respectively.
+ * appending -hid and -unhid to the keys respectively.
*
* @param int $n The new bitfield.
* @param int $o The old bitfield.
diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php
index 0eb87e4a..5770276a 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -47,6 +47,7 @@ class SearchEngine {
/** @var bool */
protected $showSuggestion = true;
+ private $sort = 'relevance';
/** @var array Feature values */
protected $features = array();
@@ -137,7 +138,7 @@ class SearchEngine {
public static function getNearMatch( $searchterm ) {
$title = self::getNearMatchInternal( $searchterm );
- wfRunHooks( 'SearchGetNearMatchComplete', array( $searchterm, &$title ) );
+ Hooks::run( 'SearchGetNearMatchComplete', array( $searchterm, &$title ) );
return $title;
}
@@ -170,7 +171,7 @@ class SearchEngine {
}
$titleResult = null;
- if ( !wfRunHooks( 'SearchGetNearMatchBefore', array( $allSearchTerms, &$titleResult ) ) ) {
+ if ( !Hooks::run( 'SearchGetNearMatchBefore', array( $allSearchTerms, &$titleResult ) ) ) {
return $titleResult;
}
@@ -197,7 +198,7 @@ class SearchEngine {
return $title;
}
- if ( !wfRunHooks( 'SearchAfterNoDirectMatch', array( $term, &$title ) ) ) {
+ if ( !Hooks::run( 'SearchAfterNoDirectMatch', array( $term, &$title ) ) ) {
return $title;
}
@@ -227,7 +228,7 @@ class SearchEngine {
// Give hooks a chance at better match variants
$title = null;
- if ( !wfRunHooks( 'SearchGetNearMatch', array( $term, &$title ) ) ) {
+ if ( !Hooks::run( 'SearchGetNearMatch', array( $term, &$title ) ) ) {
return $title;
}
}
@@ -310,6 +311,43 @@ class SearchEngine {
}
/**
+ * Get the valid sort directions. All search engines support 'relevance' but others
+ * might support more. The default in all implementations should be 'relevance.'
+ *
+ * @since 1.25
+ * @return array(string) the valid sort directions for setSort
+ */
+ public function getValidSorts() {
+ return array( 'relevance' );
+ }
+
+ /**
+ * Set the sort direction of the search results. Must be one returned by
+ * SearchEngine::getValidSorts()
+ *
+ * @since 1.25
+ * @throws InvalidArgumentException
+ * @param string $sort sort direction for query result
+ */
+ public function setSort( $sort ) {
+ if ( !in_array( $sort, $this->getValidSorts() ) ) {
+ throw new InvalidArgumentException( "Invalid sort: $sort. " .
+ "Must be one of: " . implode( ', ', $this->getValidSorts() ) );
+ }
+ $this->sort = $sort;
+ }
+
+ /**
+ * Get the sort direction of the search results
+ *
+ * @since 1.25
+ * @return string
+ */
+ public function getSort() {
+ return $this->sort;
+ }
+
+ /**
* Parse some common prefixes: all (search everything)
* or namespace names
*
@@ -356,7 +394,7 @@ class SearchEngine {
}
}
- wfRunHooks( 'SearchableNamespaces', array( &$arr ) );
+ Hooks::run( 'SearchableNamespaces', array( &$arr ) );
return $arr;
}
@@ -500,22 +538,12 @@ class SearchEngine {
/**
* Get OpenSearch suggestion template
*
+ * @deprecated since 1.25
* @return string
*/
public static function getOpenSearchTemplate() {
- global $wgOpenSearchTemplate, $wgCanonicalServer;
-
- if ( $wgOpenSearchTemplate ) {
- return $wgOpenSearchTemplate;
- } else {
- $ns = implode( '|', SearchEngine::defaultNamespaces() );
- if ( !$ns ) {
- $ns = "0";
- }
-
- return $wgCanonicalServer . wfScript( 'api' )
- . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
- }
+ wfDeprecated( __METHOD__, '1.25' );
+ return ApiOpenSearch::getOpenSearchTemplate( 'application/x-suggestions+json' );
}
/**
diff --git a/includes/search/SearchHighlighter.php b/includes/search/SearchHighlighter.php
index c3c3a8f8..5087e8d5 100644
--- a/includes/search/SearchHighlighter.php
+++ b/includes/search/SearchHighlighter.php
@@ -45,8 +45,6 @@ class SearchHighlighter {
public function highlightText( $text, $terms, $contextlines, $contextchars ) {
global $wgContLang, $wgSearchHighlightBoundaries;
- $fname = __METHOD__;
-
if ( $text == '' ) {
return '';
}
@@ -60,14 +58,14 @@ class SearchHighlighter {
3 => "/(\n\\{\\|)|(\n\\|\\})/" ); // table
// @todo FIXME: This should prolly be a hook or something
- if ( function_exists( 'wfCite' ) ) {
+ // instead of hardcoding a class name from the Cite extension
+ if ( class_exists( 'Cite' ) ) {
$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
@@ -132,8 +130,6 @@ class SearchHighlighter {
$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
@@ -163,8 +159,6 @@ class SearchHighlighter {
$pat1 = "/(" . $phrase . ")/ui";
$pat2 = "/$patPre(" . $anyterm . ")$patPost/ui";
- wfProfileIn( "$fname-extract" );
-
$left = $contextlines;
$snippets = array();
@@ -287,8 +281,6 @@ class SearchHighlighter {
}
}
- wfProfileOut( "$fname-extract" );
-
return $extract;
}
@@ -451,15 +443,6 @@ class SearchHighlighter {
* @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 );
@@ -468,13 +451,11 @@ class SearchHighlighter {
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;
}
@@ -512,7 +493,6 @@ class SearchHighlighter {
*/
public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
global $wgContLang;
- $fname = __METHOD__;
$lines = explode( "\n", $text );
@@ -523,7 +503,6 @@ class SearchHighlighter {
$lineno = 0;
$extract = "";
- wfProfileIn( "$fname-extract" );
foreach ( $lines as $line ) {
if ( 0 == $contextlines ) {
break;
@@ -551,7 +530,6 @@ class SearchHighlighter {
$extract .= "${line}\n";
}
- wfProfileOut( "$fname-extract" );
return $extract;
}
diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php
index 78eba2d0..485088cb 100644
--- a/includes/search/SearchMySQL.php
+++ b/includes/search/SearchMySQL.php
@@ -382,8 +382,6 @@ class SearchMySQL extends SearchDatabase {
function normalizeText( $string ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
-
$out = parent::normalizeText( $string );
// MySQL fulltext index doesn't grok utf-8, so we
@@ -416,8 +414,6 @@ class SearchMySQL extends SearchDatabase {
"$1u82e$2",
$out );
- wfProfileOut( __METHOD__ );
-
return $out;
}
diff --git a/includes/search/SearchPostgres.php b/includes/search/SearchPostgres.php
index 59b0c31c..bda10b0b 100644
--- a/includes/search/SearchPostgres.php
+++ b/includes/search/SearchPostgres.php
@@ -85,7 +85,7 @@ class SearchPostgres extends SearchDatabase {
if ( strtolower( $terms[2] ) === 'and' ) {
$searchstring .= ' & ';
}
- elseif ( strtolower( $terms[2] ) === 'or' or $terms[2] === '|' ) {
+ elseif ( strtolower( $terms[2] ) === 'or' || $terms[2] === '|' ) {
$searchstring .= ' | ';
}
elseif ( strtolower( $terms[2] ) === 'not' ) {
diff --git a/includes/search/SearchResult.php b/includes/search/SearchResult.php
index aeaba8df..d384ae96 100644
--- a/includes/search/SearchResult.php
+++ b/includes/search/SearchResult.php
@@ -71,7 +71,7 @@ class SearchResult {
$this->mTitle = $title;
if ( !is_null( $this->mTitle ) ) {
$id = false;
- wfRunHooks( 'SearchResultInitFromTitle', array( $title, &$id ) );
+ Hooks::run( 'SearchResultInitFromTitle', array( $title, &$id ) );
$this->mRevision = Revision::newFromTitle(
$this->mTitle, $id, Revision::READ_NORMAL );
if ( $this->mTitle->getNamespace() === NS_FILE ) {
@@ -186,6 +186,13 @@ class SearchResult {
}
/**
+ * @return string Highlighted relevant category name or '' if none or not supported
+ */
+ public function getCategorySnippet() {
+ return '';
+ }
+
+ /**
* @return string Timestamp
*/
function getTimestamp() {
diff --git a/includes/site/CachingSiteStore.php b/includes/site/CachingSiteStore.php
new file mode 100644
index 00000000..9243f12b
--- /dev/null
+++ b/includes/site/CachingSiteStore.php
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * Represents the site configuration of a wiki.
+ * Holds a list of sites (ie SiteList), with a caching layer.
+ *
+ * This 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
+ *
+ * @since 1.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class CachingSiteStore implements SiteStore {
+
+ /**
+ * @var SiteList|null
+ */
+ private $sites = null;
+
+ /**
+ * @var string|null
+ */
+ private $cacheKey;
+
+ /**
+ * @var int
+ */
+ private $cacheTimeout;
+
+ /**
+ * @var BagOStuff
+ */
+ private $cache;
+
+ /**
+ * @var SiteStore
+ */
+ private $siteStore;
+
+ /**
+ * @param SiteStore $siteStore
+ * @param BagOStuff $cache
+ * @param string|null $cacheKey
+ * @param int $cacheTimeout
+ */
+ public function __construct(
+ SiteStore $siteStore,
+ BagOStuff $cache,
+ $cacheKey = null,
+ $cacheTimeout = 3600
+ ) {
+ $this->siteStore = $siteStore;
+ $this->cache = $cache;
+ $this->cacheKey = $cacheKey;
+ $this->cacheTimeout = $cacheTimeout;
+ }
+
+ /**
+ * Constructs a cache key to use for caching the list of sites.
+ *
+ * This includes the concrete class name of the site list as well as a version identifier
+ * for the list's serialization, to avoid problems when unserializing site lists serialized
+ * by an older version, e.g. when reading from a cache.
+ *
+ * The cache key also includes information about where the sites were loaded from, e.g.
+ * the name of a database table.
+ *
+ * @see SiteList::getSerialVersionId
+ *
+ * @return string The cache key.
+ */
+ private function getCacheKey() {
+ if ( $this->cacheKey === null ) {
+ $type = 'SiteList#' . SiteList::getSerialVersionId();
+ $this->cacheKey = wfMemcKey( "sites/$type" );
+ }
+
+ return $this->cacheKey;
+ }
+
+ /**
+ * @see SiteStore::getSites
+ *
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites() {
+ if ( $this->sites === null ) {
+ $this->sites = $this->cache->get( $this->getCacheKey() );
+
+ if ( !is_object( $this->sites ) ) {
+ $this->sites = $this->siteStore->getSites();
+
+ $this->cache->set( $this->getCacheKey(), $this->sites, $this->cacheTimeout );
+ }
+ }
+
+ return $this->sites;
+ }
+
+ /**
+ * @see SiteStore::getSite
+ *
+ * @since 1.25
+ *
+ * @param string $globalId
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId ) {
+ $sites = $this->getSites();
+
+ return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @see SiteStore::saveSite
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return bool Success indicator
+ */
+ public function saveSite( Site $site ) {
+ return $this->saveSites( array( $site ) );
+ }
+
+ /**
+ * @see SiteStore::saveSites
+ *
+ * @since 1.25
+ *
+ * @param Site[] $sites
+ *
+ * @return bool Success indicator
+ */
+ public function saveSites( array $sites ) {
+ if ( empty( $sites ) ) {
+ return true;
+ }
+
+ $success = $this->siteStore->saveSites( $sites );
+
+ // purge cache
+ $this->reset();
+
+ return $success;
+ }
+
+ /**
+ * Purges the internal and external cache of the site list, forcing the list
+ * of sites to be reloaded.
+ *
+ * @since 1.25
+ */
+ public function reset() {
+ // purge cache
+ $this->cache->delete( $this->getCacheKey() );
+ $this->sites = null;
+ }
+
+ /**
+ * Clears the list of sites stored.
+ *
+ * @see SiteStore::clear()
+ *
+ * @return bool Success
+ */
+ public function clear() {
+ $this->reset();
+
+ return $this->siteStore->clear();
+ }
+
+}
diff --git a/includes/site/DBSiteStore.php b/includes/site/DBSiteStore.php
new file mode 100644
index 00000000..f167584e
--- /dev/null
+++ b/includes/site/DBSiteStore.php
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * Represents the site configuration of a wiki.
+ * Holds a list of sites (ie SiteList), stored in the database.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class DBSiteStore implements SiteStore {
+
+ /**
+ * @var SiteList|null
+ */
+ protected $sites = null;
+
+ /**
+ * @var ORMTable
+ */
+ protected $sitesTable;
+
+ /**
+ * @since 1.25
+ *
+ * @param ORMTable|null $sitesTable
+ */
+ public function __construct( ORMTable $sitesTable = null ) {
+ if ( $sitesTable === null ) {
+ $sitesTable = $this->newSitesTable();
+ }
+
+ $this->sitesTable = $sitesTable;
+ }
+
+ /**
+ * @see SiteStore::getSites
+ *
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites() {
+ $this->loadSites();
+
+ return $this->sites;
+ }
+
+ /**
+ * Returns a new Site object constructed from the provided ORMRow.
+ *
+ * @since 1.25
+ *
+ * @param ORMRow $siteRow
+ *
+ * @return Site
+ */
+ protected function siteFromRow( ORMRow $siteRow ) {
+
+ $site = Site::newForType( $siteRow->getField( 'type', Site::TYPE_UNKNOWN ) );
+
+ $site->setGlobalId( $siteRow->getField( 'global_key' ) );
+
+ $site->setInternalId( $siteRow->getField( 'id' ) );
+
+ if ( $siteRow->hasField( 'forward' ) ) {
+ $site->setForward( $siteRow->getField( 'forward' ) );
+ }
+
+ if ( $siteRow->hasField( 'group' ) ) {
+ $site->setGroup( $siteRow->getField( 'group' ) );
+ }
+
+ if ( $siteRow->hasField( 'language' ) ) {
+ $site->setLanguageCode( $siteRow->getField( 'language' ) === ''
+ ? null
+ : $siteRow->getField( 'language' )
+ );
+ }
+
+ if ( $siteRow->hasField( 'source' ) ) {
+ $site->setSource( $siteRow->getField( 'source' ) );
+ }
+
+ if ( $siteRow->hasField( 'data' ) ) {
+ $site->setExtraData( $siteRow->getField( 'data' ) );
+ }
+
+ if ( $siteRow->hasField( 'config' ) ) {
+ $site->setExtraConfig( $siteRow->getField( 'config' ) );
+ }
+
+ return $site;
+ }
+
+ /**
+ * Get a new ORMRow from a Site object
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return ORMRow
+ */
+ protected function getRowFromSite( Site $site ) {
+ $fields = array(
+ // Site data
+ 'global_key' => $site->getGlobalId(), // TODO: check not null
+ 'type' => $site->getType(),
+ 'group' => $site->getGroup(),
+ 'source' => $site->getSource(),
+ 'language' => $site->getLanguageCode() === null ? '' : $site->getLanguageCode(),
+ 'protocol' => $site->getProtocol(),
+ 'domain' => strrev( $site->getDomain() ) . '.',
+ 'data' => $site->getExtraData(),
+
+ // Site config
+ 'forward' => $site->shouldForward(),
+ 'config' => $site->getExtraConfig(),
+ );
+
+ if ( $site->getInternalId() !== null ) {
+ $fields['id'] = $site->getInternalId();
+ }
+
+ return new ORMRow( $this->sitesTable, $fields );
+ }
+
+ /**
+ * Fetches the site from the database and loads them into the sites field.
+ *
+ * @since 1.25
+ */
+ protected function loadSites() {
+ $this->sites = new SiteList();
+
+ foreach ( $this->sitesTable->select() as $siteRow ) {
+ $this->sites[] = $this->siteFromRow( $siteRow );
+ }
+
+ // Batch load the local site identifiers.
+ $ids = wfGetDB( $this->sitesTable->getReadDb() )->select(
+ 'site_identifiers',
+ array(
+ 'si_site',
+ 'si_type',
+ 'si_key',
+ ),
+ array(),
+ __METHOD__
+ );
+
+ foreach ( $ids as $id ) {
+ if ( $this->sites->hasInternalId( $id->si_site ) ) {
+ $site = $this->sites->getSiteByInternalId( $id->si_site );
+ $site->addLocalId( $id->si_type, $id->si_key );
+ $this->sites->setSite( $site );
+ }
+ }
+ }
+
+ /**
+ * @see SiteStore::getSite
+ *
+ * @since 1.25
+ *
+ * @param string $globalId
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId ) {
+ if ( $this->sites === null ) {
+ $this->sites = $this->getSites();
+ }
+
+ return $this->sites->hasSite( $globalId ) ? $this->sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @see SiteStore::saveSite
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return bool Success indicator
+ */
+ public function saveSite( Site $site ) {
+ return $this->saveSites( array( $site ) );
+ }
+
+ /**
+ * @see SiteStore::saveSites
+ *
+ * @since 1.25
+ *
+ * @param Site[] $sites
+ *
+ * @return bool Success indicator
+ */
+ public function saveSites( array $sites ) {
+ if ( empty( $sites ) ) {
+ return true;
+ }
+
+ $dbw = $this->sitesTable->getWriteDbConnection();
+
+ $dbw->startAtomic( __METHOD__ );
+
+ $success = true;
+
+ $internalIds = array();
+ $localIds = array();
+
+ foreach ( $sites as $site ) {
+ if ( $site->getInternalId() !== null ) {
+ $internalIds[] = $site->getInternalId();
+ }
+
+ $siteRow = $this->getRowFromSite( $site );
+ $success = $siteRow->save( __METHOD__ ) && $success;
+
+ foreach ( $site->getLocalIds() as $idType => $ids ) {
+ foreach ( $ids as $id ) {
+ $localIds[] = array( $siteRow->getId(), $idType, $id );
+ }
+ }
+ }
+
+ if ( $internalIds !== array() ) {
+ $dbw->delete(
+ 'site_identifiers',
+ array( 'si_site' => $internalIds ),
+ __METHOD__
+ );
+ }
+
+ foreach ( $localIds as $localId ) {
+ $dbw->insert(
+ 'site_identifiers',
+ array(
+ 'si_site' => $localId[0],
+ 'si_type' => $localId[1],
+ 'si_key' => $localId[2],
+ ),
+ __METHOD__
+ );
+ }
+
+ $dbw->endAtomic( __METHOD__ );
+
+ $this->reset();
+
+ return $success;
+ }
+
+ /**
+ * Resets the SiteList
+ *
+ * @since 1.25
+ */
+ public function reset() {
+ $this->sites = null;
+ }
+
+ /**
+ * Clears the list of sites stored in the database.
+ *
+ * @see SiteStore::clear()
+ *
+ * @return bool Success
+ */
+ public function clear() {
+ $dbw = $this->sitesTable->getWriteDbConnection();
+
+ $dbw->startAtomic( __METHOD__ );
+ $ok = $dbw->delete( 'sites', '*', __METHOD__ );
+ $ok = $dbw->delete( 'site_identifiers', '*', __METHOD__ ) && $ok;
+ $dbw->endAtomic( __METHOD__ );
+
+ $this->reset();
+
+ return $ok;
+ }
+
+ /**
+ * @since 1.25
+ *
+ * @return ORMTable
+ */
+ protected function newSitesTable() {
+ return new ORMTable(
+ 'sites',
+ array(
+ 'id' => 'id',
+
+ // Site data
+ 'global_key' => 'str',
+ 'type' => 'str',
+ 'group' => 'str',
+ 'source' => 'str',
+ 'language' => 'str',
+ 'protocol' => 'str',
+ 'domain' => 'str',
+ 'data' => 'array',
+
+ // Site config
+ 'forward' => 'bool',
+ 'config' => 'array',
+ ),
+ array(
+ 'type' => Site::TYPE_UNKNOWN,
+ 'group' => Site::GROUP_NONE,
+ 'source' => Site::SOURCE_LOCAL,
+ 'data' => array(),
+
+ 'forward' => false,
+ 'config' => array(),
+ 'language' => '',
+ ),
+ 'ORMRow',
+ 'site_'
+ );
+ }
+
+}
diff --git a/includes/site/FileBasedSiteLookup.php b/includes/site/FileBasedSiteLookup.php
new file mode 100644
index 00000000..96544403
--- /dev/null
+++ b/includes/site/FileBasedSiteLookup.php
@@ -0,0 +1,139 @@
+<?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
+ *
+ * @license GNU GPL v2+
+ */
+
+/**
+ * Provides a file-based cache of a SiteStore. The sites are stored in
+ * a json file. (see docs/sitescache.txt regarding format)
+ *
+ * The cache can be built with the rebuildSitesCache.php maintenance script,
+ * and a MediaWiki instance can be setup to use this by setting the
+ * 'wgSitesCacheFile' configuration to the cache file location.
+ *
+ * @since 1.25
+ */
+class FileBasedSiteLookup implements SiteLookup {
+
+ /**
+ * @var SiteList
+ */
+ private $sites = null;
+
+ /**
+ * @var string
+ */
+ private $cacheFile;
+
+ /**
+ * @param string $cacheFile
+ */
+ public function __construct( $cacheFile ) {
+ $this->cacheFile = $cacheFile;
+ }
+
+ /**
+ * @since 1.25
+ *
+ * @return SiteList
+ */
+ public function getSites() {
+ if ( $this->sites === null ) {
+ $this->sites = $this->loadSitesFromCache();
+ }
+
+ return $this->sites;
+ }
+
+ /**
+ * @param string $globalId
+ *
+ * @since 1.25
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId ) {
+ $sites = $this->getSites();
+
+ return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
+ }
+
+ /**
+ * @return SiteList
+ */
+ private function loadSitesFromCache() {
+ $data = $this->loadJsonFile();
+
+ $sites = new SiteList();
+
+ // @todo lazy initialize the site objects in the site list (e.g. only when needed to access)
+ foreach ( $data['sites'] as $siteArray ) {
+ $sites[] = $this->newSiteFromArray( $siteArray );
+ }
+
+ return $sites;
+ }
+
+ /**
+ * @throws MWException
+ * @return array see docs/sitescache.txt for format of the array.
+ */
+ private function loadJsonFile() {
+ if ( !is_readable( $this->cacheFile ) ) {
+ throw new MWException( 'SiteList cache file not found.' );
+ }
+
+ $contents = file_get_contents( $this->cacheFile );
+ $data = json_decode( $contents, true );
+
+ if ( !is_array( $data ) || !is_array( $data['sites'] )
+ || !array_key_exists( 'sites', $data )
+ ) {
+ throw new MWException( 'SiteStore json cache data is invalid.' );
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return Site
+ */
+ private function newSiteFromArray( array $data ) {
+ $siteType = array_key_exists( 'type', $data ) ? $data['type'] : Site::TYPE_UNKNOWN;
+ $site = Site::newForType( $siteType );
+
+ $site->setGlobalId( $data['globalid'] );
+ $site->setForward( $data['forward'] );
+ $site->setGroup( $data['group'] );
+ $site->setLanguageCode( $data['language'] );
+ $site->setSource( $data['source'] );
+ $site->setExtraData( $data['data'] );
+ $site->setExtraConfig( $data['config'] );
+
+ foreach ( $data['identifiers'] as $identifier ) {
+ $site->addLocalId( $identifier['type'], $identifier['key'] );
+ }
+
+ return $site;
+ }
+
+}
diff --git a/includes/site/HashSiteStore.php b/includes/site/HashSiteStore.php
new file mode 100644
index 00000000..2c254721
--- /dev/null
+++ b/includes/site/HashSiteStore.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * In-memory implementation of SiteStore.
+ *
+ * This 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
+ */
+
+/**
+ * In-memory SiteStore implementation, storing sites in an associative array.
+ *
+ * @author Daniel Kinzler
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ *
+ * @since 1.25
+ * @ingroup Site
+ */
+class HashSiteStore implements SiteStore {
+
+ /**
+ * @var Site[]
+ */
+ private $sites = array();
+
+ /**
+ * @param array $sites
+ */
+ public function __construct( $sites = array() ) {
+ $this->saveSites( $sites );
+ }
+
+ /**
+ * Saves the provided site.
+ *
+ * @since 1.25
+ *
+ * @param Site $site
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSite( Site $site ) {
+ $this->sites[$site->getGlobalId()] = $site;
+
+ return true;
+ }
+
+ /**
+ * Saves the provided sites.
+ *
+ * @since 1.25
+ *
+ * @param Site[] $sites
+ *
+ * @return boolean Success indicator
+ */
+ public function saveSites( array $sites ) {
+ foreach ( $sites as $site ) {
+ $this->saveSite( $site );
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the site with provided global id, or null if there is no such site.
+ *
+ * @since 1.25
+ *
+ * @param string $globalId
+ * @param string $source either 'cache' or 'recache'.
+ * If 'cache', the values can (but not obliged) come from a cache.
+ *
+ * @return Site|null
+ */
+ public function getSite( $globalId, $source = 'cache' ) {
+ if ( isset( $this->sites[$globalId] ) ) {
+ return $this->sites[$globalId];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of all sites. By default this site is
+ * fetched from the cache, which can be changed to loading
+ * the list from the database using the $useCache parameter.
+ *
+ * @since 1.25
+ *
+ * @param string $source either 'cache' or 'recache'.
+ * If 'cache', the values can (but not obliged) come from a cache.
+ *
+ * @return SiteList
+ */
+ public function getSites( $source = 'cache' ) {
+ return new SiteList( $this->sites );
+ }
+
+ /**
+ * Deletes all sites from the database. After calling clear(), getSites() will return an empty
+ * list and getSite() will return null until saveSite() or saveSites() is called.
+ */
+ public function clear() {
+ $this->sites = array();
+
+ return true;
+ }
+
+}
diff --git a/includes/site/MediaWikiSite.php b/includes/site/MediaWikiSite.php
index 9711f042..95631f8e 100644
--- a/includes/site/MediaWikiSite.php
+++ b/includes/site/MediaWikiSite.php
@@ -116,7 +116,7 @@ class MediaWikiSite extends Site {
// Make sure the string is normalized into NFC (due to the bug 40017)
// but do nothing to the whitespaces, that should work appropriately.
// @see https://bugzilla.wikimedia.org/show_bug.cgi?id=40017
- $pageName = UtfNormal::cleanUp( $pageName );
+ $pageName = UtfNormal\Validator::cleanUp( $pageName );
// Build the args for the specific call
$args = array(
@@ -137,7 +137,7 @@ class MediaWikiSite extends Site {
// Go on call the external site
// @todo we need a good way to specify a timeout here.
- $ret = Http::get( $url );
+ $ret = Http::get( $url, array(), __METHOD__ );
}
if ( $ret === false ) {
diff --git a/includes/site/SiteExporter.php b/includes/site/SiteExporter.php
new file mode 100644
index 00000000..62f6ca3c
--- /dev/null
+++ b/includes/site/SiteExporter.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * Utility for exporting site entries to XML.
+ * For the output file format, see docs/sitelist.txt and docs/sitelist-1.0.xsd.
+ *
+ * This 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
+ *
+ * @since 1.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class SiteExporter {
+
+ /**
+ * @var resource
+ */
+ private $sink;
+
+ /**
+ * @param resource $sink A file handle open for writing
+ */
+ public function __construct( $sink ) {
+ if ( !is_resource( $sink ) || get_resource_type( $sink ) !== 'stream' ) {
+ throw new InvalidArgumentException( '$sink must be a file handle' );
+ }
+
+ $this->sink = $sink;
+ }
+
+ /**
+ * Writes a <site> tag for each Site object in $sites, and encloses the entire list
+ * between <sites> tags.
+ *
+ * @param Site[]|SiteList $sites
+ */
+ public function exportSites( $sites ) {
+ $attributes = array(
+ 'version' => '1.0',
+ 'xmlns' => 'http://www.mediawiki.org/xml/sitelist-1.0/',
+ );
+
+ fwrite( $this->sink, XML::openElement( 'sites', $attributes ) . "\n" );
+
+ foreach ( $sites as $site ) {
+ $this->exportSite( $site );
+ }
+
+ fwrite( $this->sink, XML::closeElement( 'sites' ) . "\n" );
+ fflush( $this->sink );
+ }
+
+ /**
+ * Writes a <site> tag representing the given Site object.
+ *
+ * @param Site $site
+ */
+ private function exportSite( Site $site ) {
+ if ( $site->getType() !== Site::TYPE_UNKNOWN ) {
+ $siteAttr = array( 'type' => $site->getType() );
+ } else {
+ $siteAttr = null;
+ }
+
+ fwrite( $this->sink, "\t" . XML::openElement( 'site', $siteAttr ) . "\n" );
+
+ fwrite( $this->sink, "\t\t" . XML::element( 'globalid', null, $site->getGlobalId() ) . "\n" );
+
+ if ( $site->getGroup() !== Site::GROUP_NONE ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'group', null, $site->getGroup() ) . "\n" );
+ }
+
+ if ( $site->getSource() !== Site::SOURCE_LOCAL ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'source', null, $site->getSource() ) . "\n" );
+ }
+
+ if ( $site->shouldForward() ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'forward', null, '' ) . "\n" );
+ }
+
+ foreach ( $site->getAllPaths() as $type => $path ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'path', array( 'type' => $type ), $path ) . "\n" );
+ }
+
+ foreach ( $site->getLocalIds() as $type => $ids ) {
+ foreach ( $ids as $id ) {
+ fwrite( $this->sink, "\t\t" . XML::element( 'localid', array( 'type' => $type ), $id ) . "\n" );
+ }
+ }
+
+ //@todo: export <data>
+ //@todo: export <config>
+
+ fwrite( $this->sink, "\t" . XML::closeElement( 'site' ) . "\n" );
+ }
+
+}
diff --git a/includes/site/SiteImporter.php b/includes/site/SiteImporter.php
new file mode 100644
index 00000000..a05bad5d
--- /dev/null
+++ b/includes/site/SiteImporter.php
@@ -0,0 +1,263 @@
+<?php
+
+/**
+ * Utility for importing site entries from XML.
+ * For the expected format of the input, see docs/sitelist.txt and docs/sitelist-1.0.xsd.
+ *
+ * This 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
+ *
+ * @since 1.25
+ *
+ * @file
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
+ * @author Daniel Kinzler
+ */
+class SiteImporter {
+
+ /**
+ * @var SiteStore
+ */
+ private $store;
+
+ /**
+ * @var callable|null
+ */
+ private $exceptionCallback;
+
+ /**
+ * @param SiteStore $store
+ */
+ public function __construct( SiteStore $store ) {
+ $this->store = $store;
+ }
+
+ /**
+ * @return callable
+ */
+ public function getExceptionCallback() {
+ return $this->exceptionCallback;
+ }
+
+ /**
+ * @param callable $exceptionCallback
+ */
+ public function setExceptionCallback( $exceptionCallback ) {
+ $this->exceptionCallback = $exceptionCallback;
+ }
+
+ /**
+ * @param string $file
+ */
+ public function importFromFile( $file ) {
+ $xml = file_get_contents( $file );
+
+ if ( $xml === false ) {
+ throw new RuntimeException( 'Failed to read ' . $file . '!' );
+ }
+
+ $this->importFromXML( $xml );
+ }
+
+ /**
+ * @param string $xml
+ *
+ * @throws InvalidArgumentException
+ */
+ public function importFromXML( $xml ) {
+ $document = new DOMDocument();
+
+ $oldLibXmlErrors = libxml_use_internal_errors( true );
+ $ok = $document->loadXML( $xml, LIBXML_NONET );
+
+ if ( !$ok ) {
+ $errors = libxml_get_errors();
+ libxml_use_internal_errors( $oldLibXmlErrors );
+
+ foreach ( $errors as $error ) {
+ /** @var LibXMLError $error */
+ throw new InvalidArgumentException(
+ 'Malformed XML: ' . $error->message . ' in line ' . $error->line
+ );
+ }
+
+ throw new InvalidArgumentException( 'Malformed XML!' );
+ }
+
+ libxml_use_internal_errors( $oldLibXmlErrors );
+ $this->importFromDOM( $document->documentElement );
+ }
+
+ /**
+ * @param DOMElement $root
+ */
+ private function importFromDOM( DOMElement $root ) {
+ $sites = $this->makeSiteList( $root );
+ $this->store->saveSites( $sites );
+ }
+
+ /**
+ * @param DOMElement $root
+ *
+ * @return Site[]
+ */
+ private function makeSiteList( DOMElement $root ) {
+ $sites = array();
+
+ // Old sites, to get the row IDs that correspond to the global site IDs.
+ // TODO: Get rid of internal row IDs, they just get in the way. Get rid of ORMRow, too.
+ $oldSites = $this->store->getSites();
+
+ $current = $root->firstChild;
+ while ( $current ) {
+ if ( $current instanceof DOMElement && $current->tagName === 'site' ) {
+ try {
+ $site = $this->makeSite( $current );
+ $key = $site->getGlobalId();
+
+ if ( $oldSites->hasSite( $key ) ) {
+ $oldSite = $oldSites->getSite( $key );
+ $site->setInternalId( $oldSite->getInternalId() );
+ }
+
+ $sites[$key] = $site;
+ } catch ( Exception $ex ) {
+ $this->handleException( $ex );
+ }
+ }
+
+ $current = $current->nextSibling;
+ }
+
+ return $sites;
+ }
+
+ /**
+ * @param DOMElement $siteElement
+ *
+ * @return Site
+ * @throws InvalidArgumentException
+ */
+ public function makeSite( DOMElement $siteElement ) {
+ if ( $siteElement->tagName !== 'site' ) {
+ throw new InvalidArgumentException( 'Expected <site> tag, found ' . $siteElement->tagName );
+ }
+
+ $type = $this->getAttributeValue( $siteElement, 'type', Site::TYPE_UNKNOWN );
+ $site = Site::newForType( $type );
+
+ $site->setForward( $this->hasChild( $siteElement, 'forward' ) );
+ $site->setGlobalId( $this->getChildText( $siteElement, 'globalid' ) );
+ $site->setGroup( $this->getChildText( $siteElement, 'group', Site::GROUP_NONE ) );
+ $site->setSource( $this->getChildText( $siteElement, 'source', Site::SOURCE_LOCAL ) );
+
+ $pathTags = $siteElement->getElementsByTagName( 'path' );
+ for ( $i = 0; $i < $pathTags->length; $i++ ) {
+ $pathElement = $pathTags->item( $i );
+ $pathType = $this->getAttributeValue( $pathElement, 'type' );
+ $path = $pathElement->textContent;
+
+ $site->setPath( $pathType, $path );
+ }
+
+ $idTags = $siteElement->getElementsByTagName( 'localid' );
+ for ( $i = 0; $i < $idTags->length; $i++ ) {
+ $idElement = $idTags->item( $i );
+ $idType = $this->getAttributeValue( $idElement, 'type' );
+ $id = $idElement->textContent;
+
+ $site->addLocalId( $idType, $id );
+ }
+
+ //@todo: import <data>
+ //@todo: import <config>
+
+ return $site;
+ }
+
+ /**
+ * @param DOMElement $element
+ * @param $name
+ * @param string|null|bool $default
+ *
+ * @return null|string
+ * @throws MWException If the attribute is not found and no default is provided
+ */
+ private function getAttributeValue( DOMElement $element, $name, $default = false ) {
+ $node = $element->getAttributeNode( $name );
+
+ if ( !$node ) {
+ if ( $default !== false ) {
+ return $default;
+ } else {
+ throw new MWException(
+ 'Required ' . $name . ' attribute not found in <' . $element->tagName . '> tag'
+ );
+ }
+ }
+
+ return $node->textContent;
+ }
+
+ /**
+ * @param DOMElement $element
+ * @param string $name
+ * @param string|null|bool $default
+ *
+ * @return null|string
+ * @throws MWException If the child element is not found and no default is provided
+ */
+ private function getChildText( DOMElement $element, $name, $default = false ) {
+ $elements = $element->getElementsByTagName( $name );
+
+ if ( $elements->length < 1 ) {
+ if ( $default !== false ) {
+ return $default;
+ } else {
+ throw new MWException(
+ 'Required <' . $name . '> tag not found inside <' . $element->tagName . '> tag'
+ );
+ }
+ }
+
+ $node = $elements->item( 0 );
+ return $node->textContent;
+ }
+
+ /**
+ * @param DOMElement $element
+ * @param string $name
+ *
+ * @return bool
+ * @throws MWException
+ */
+ private function hasChild( DOMElement $element, $name ) {
+ return $this->getChildText( $element, $name, null ) !== null;
+ }
+
+ /**
+ * @param Exception $ex
+ */
+ private function handleException( Exception $ex ) {
+ if ( $this->exceptionCallback ) {
+ call_user_func( $this->exceptionCallback, $ex );
+ } else {
+ wfLogWarning( $ex->getMessage() );
+ }
+ }
+
+}
diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/site/SiteLookup.php
index 61927d77..610bf0b7 100644
--- a/includes/resourceloader/ResourceLoaderNoscriptModule.php
+++ b/includes/site/SiteLookup.php
@@ -1,6 +1,7 @@
<?php
+
/**
- * Resource loader for site customizations for users without JavaScript enabled.
+ * Interface for service objects providing a lookup of Site 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
@@ -17,38 +18,33 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @since 1.25
+ *
* @file
- * @author Trevor Parscal
- * @author Roan Kattouw
- */
-
-/**
- * Module for site customizations
+ * @ingroup Site
+ *
+ * @license GNU GPL v2+
*/
-class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
-
- /* Protected Methods */
+interface SiteLookup {
/**
- * Gets list of pages used by this module. Obviously, it makes absolutely no
- * sense to include JavaScript files here... :D
+ * Returns the site with provided global id, or null if there is no such site.
+ *
+ * @since 1.25
*
- * @param ResourceLoaderContext $context
+ * @param string $globalId
*
- * @return array List of pages
+ * @return Site|null
*/
- protected function getPages( ResourceLoaderContext $context ) {
- return array( 'MediaWiki:Noscript.css' => array( 'type' => 'style' ) );
- }
-
- /* Methods */
+ public function getSite( $globalId );
/**
- * Gets group name
+ * Returns a list of all sites.
*
- * @return string Name of group
+ * @since 1.25
+ *
+ * @return SiteList
*/
- public function getGroup() {
- return 'noscript';
- }
+ public function getSites();
+
}
diff --git a/includes/site/SiteSQLStore.php b/includes/site/SiteSQLStore.php
index d1334680..d77f07be 100644
--- a/includes/site/SiteSQLStore.php
+++ b/includes/site/SiteSQLStore.php
@@ -28,468 +28,25 @@
* @license GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-class SiteSQLStore implements SiteStore {
- /**
- * @since 1.21
- *
- * @var SiteList|null
- */
- protected $sites = null;
-
- /**
- * @var ORMTable
- */
- protected $sitesTable;
-
- /**
- * @var string|null
- */
- private $cacheKey = null;
-
- /**
- * @var int
- */
- private $cacheTimeout = 3600;
+class SiteSQLStore extends CachingSiteStore {
/**
* @since 1.21
+ * @deprecated 1.25 Construct a SiteStore instance directly instead.
*
* @param ORMTable|null $sitesTable
+ * @param BagOStuff|null $cache
*
* @return SiteStore
*/
- public static function newInstance( ORMTable $sitesTable = null ) {
- return new static( $sitesTable );
- }
-
- /**
- * Constructor.
- *
- * @since 1.21
- *
- * @param ORMTable|null $sitesTable
- */
- protected function __construct( ORMTable $sitesTable = null ) {
- if ( $sitesTable === null ) {
- $sitesTable = $this->newSitesTable();
- }
-
- $this->sitesTable = $sitesTable;
- }
-
- /**
- * Constructs a cache key to use for caching the list of sites.
- *
- * This includes the concrete class name of the site list as well as a version identifier
- * for the list's serialization, to avoid problems when unserializing site lists serialized
- * by an older version, e.g. when reading from a cache.
- *
- * The cache key also includes information about where the sites were loaded from, e.g.
- * the name of a database table.
- *
- * @see SiteList::getSerialVersionId
- *
- * @return string The cache key.
- */
- protected function getCacheKey() {
- wfProfileIn( __METHOD__ );
-
- if ( $this->cacheKey === null ) {
- $type = 'SiteList#' . SiteList::getSerialVersionId();
- $source = $this->sitesTable->getName();
-
- if ( $this->sitesTable->getTargetWiki() !== false ) {
- $source = $this->sitesTable->getTargetWiki() . '.' . $source;
- }
-
- $this->cacheKey = wfMemcKey( "$source/$type" );
- }
-
- wfProfileOut( __METHOD__ );
- return $this->cacheKey;
- }
-
- /**
- * @see SiteStore::getSites
- *
- * @since 1.21
- *
- * @param string $source Either 'cache' or 'recache'
- *
- * @return SiteList
- */
- public function getSites( $source = 'cache' ) {
- wfProfileIn( __METHOD__ );
-
- if ( $source === 'cache' ) {
- if ( $this->sites === null ) {
- $cache = wfGetMainCache();
- $sites = $cache->get( $this->getCacheKey() );
-
- if ( is_object( $sites ) ) {
- $this->sites = $sites;
- } else {
- $this->loadSites();
- }
- }
- }
- else {
- $this->loadSites();
- }
-
- wfProfileOut( __METHOD__ );
- return $this->sites;
- }
-
- /**
- * Returns a new Site object constructed from the provided ORMRow.
- *
- * @since 1.21
- *
- * @param ORMRow $siteRow
- *
- * @return Site
- */
- protected function siteFromRow( ORMRow $siteRow ) {
- wfProfileIn( __METHOD__ );
-
- $site = Site::newForType( $siteRow->getField( 'type', Site::TYPE_UNKNOWN ) );
-
- $site->setGlobalId( $siteRow->getField( 'global_key' ) );
-
- $site->setInternalId( $siteRow->getField( 'id' ) );
-
- if ( $siteRow->hasField( 'forward' ) ) {
- $site->setForward( $siteRow->getField( 'forward' ) );
- }
-
- if ( $siteRow->hasField( 'group' ) ) {
- $site->setGroup( $siteRow->getField( 'group' ) );
- }
-
- if ( $siteRow->hasField( 'language' ) ) {
- $site->setLanguageCode( $siteRow->getField( 'language' ) === ''
- ? null
- : $siteRow->getField( 'language' )
- );
- }
-
- if ( $siteRow->hasField( 'source' ) ) {
- $site->setSource( $siteRow->getField( 'source' ) );
- }
-
- if ( $siteRow->hasField( 'data' ) ) {
- $site->setExtraData( $siteRow->getField( 'data' ) );
- }
-
- if ( $siteRow->hasField( 'config' ) ) {
- $site->setExtraConfig( $siteRow->getField( 'config' ) );
- }
-
- wfProfileOut( __METHOD__ );
- return $site;
- }
-
- /**
- * Get a new ORMRow from a Site object
- *
- * @since 1.22
- *
- * @param Site $site
- *
- * @return ORMRow
- */
- protected function getRowFromSite( Site $site ) {
- $fields = array(
- // Site data
- 'global_key' => $site->getGlobalId(), // TODO: check not null
- 'type' => $site->getType(),
- 'group' => $site->getGroup(),
- 'source' => $site->getSource(),
- 'language' => $site->getLanguageCode() === null ? '' : $site->getLanguageCode(),
- 'protocol' => $site->getProtocol(),
- 'domain' => strrev( $site->getDomain() ) . '.',
- 'data' => $site->getExtraData(),
-
- // Site config
- 'forward' => $site->shouldForward(),
- 'config' => $site->getExtraConfig(),
- );
-
- if ( $site->getInternalId() !== null ) {
- $fields['id'] = $site->getInternalId();
- }
-
- return new ORMRow( $this->sitesTable, $fields );
- }
-
- /**
- * Fetches the site from the database and loads them into the sites field.
- *
- * @since 1.21
- */
- protected function loadSites() {
- wfProfileIn( __METHOD__ );
-
- $this->sites = new SiteList();
-
- foreach ( $this->sitesTable->select() as $siteRow ) {
- $this->sites[] = $this->siteFromRow( $siteRow );
- }
-
- // Batch load the local site identifiers.
- $ids = wfGetDB( $this->sitesTable->getReadDb() )->select(
- 'site_identifiers',
- array(
- 'si_site',
- 'si_type',
- 'si_key',
- ),
- array(),
- __METHOD__
- );
-
- foreach ( $ids as $id ) {
- if ( $this->sites->hasInternalId( $id->si_site ) ) {
- $site = $this->sites->getSiteByInternalId( $id->si_site );
- $site->addLocalId( $id->si_type, $id->si_key );
- $this->sites->setSite( $site );
- }
- }
-
- $cache = wfGetMainCache();
- $cache->set( $this->getCacheKey(), $this->sites, $this->cacheTimeout );
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * @see SiteStore::getSite
- *
- * @since 1.21
- *
- * @param string $globalId
- * @param string $source
- *
- * @return Site|null
- */
- public function getSite( $globalId, $source = 'cache' ) {
- wfProfileIn( __METHOD__ );
-
- $sites = $this->getSites( $source );
-
- wfProfileOut( __METHOD__ );
- return $sites->hasSite( $globalId ) ? $sites->getSite( $globalId ) : null;
- }
-
- /**
- * @see SiteStore::saveSite
- *
- * @since 1.21
- *
- * @param Site $site
- *
- * @return bool Success indicator
- */
- public function saveSite( Site $site ) {
- return $this->saveSites( array( $site ) );
- }
-
- /**
- * @see SiteStore::saveSites
- *
- * @since 1.21
- *
- * @param Site[] $sites
- *
- * @return bool Success indicator
- */
- public function saveSites( array $sites ) {
- wfProfileIn( __METHOD__ );
-
- if ( empty( $sites ) ) {
- wfProfileOut( __METHOD__ );
- return true;
- }
-
- $dbw = $this->sitesTable->getWriteDbConnection();
-
- $dbw->startAtomic( __METHOD__ );
-
- $success = true;
-
- $internalIds = array();
- $localIds = array();
-
- foreach ( $sites as $site ) {
- if ( $site->getInternalId() !== null ) {
- $internalIds[] = $site->getInternalId();
- }
-
- $siteRow = $this->getRowFromSite( $site );
- $success = $siteRow->save( __METHOD__ ) && $success;
-
- foreach ( $site->getLocalIds() as $idType => $ids ) {
- foreach ( $ids as $id ) {
- $localIds[] = array( $siteRow->getId(), $idType, $id );
- }
- }
- }
-
- if ( $internalIds !== array() ) {
- $dbw->delete(
- 'site_identifiers',
- array( 'si_site' => $internalIds ),
- __METHOD__
- );
- }
-
- foreach ( $localIds as $localId ) {
- $dbw->insert(
- 'site_identifiers',
- array(
- 'si_site' => $localId[0],
- 'si_type' => $localId[1],
- 'si_key' => $localId[2],
- ),
- __METHOD__
- );
+ public static function newInstance( ORMTable $sitesTable = null, BagOStuff $cache = null ) {
+ if ( $cache === null ) {
+ $cache = wfGetMainCache();
}
- $dbw->endAtomic( __METHOD__ );
+ $siteStore = new DBSiteStore();
- // purge cache
- $this->reset();
-
- wfProfileOut( __METHOD__ );
- return $success;
+ return new static( $siteStore, $cache );
}
- /**
- * Purges the internal and external cache of the site list, forcing the list
- * of sites to be re-read from the database.
- *
- * @since 1.21
- */
- public function reset() {
- wfProfileIn( __METHOD__ );
- // purge cache
- $cache = wfGetMainCache();
- $cache->delete( $this->getCacheKey() );
- $this->sites = null;
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Clears the list of sites stored in the database.
- *
- * @see SiteStore::clear()
- *
- * @return bool Success
- */
- public function clear() {
- wfProfileIn( __METHOD__ );
- $dbw = $this->sitesTable->getWriteDbConnection();
-
- $dbw->startAtomic( __METHOD__ );
- $ok = $dbw->delete( 'sites', '*', __METHOD__ );
- $ok = $dbw->delete( 'site_identifiers', '*', __METHOD__ ) && $ok;
- $dbw->endAtomic( __METHOD__);
-
- $this->reset();
-
- wfProfileOut( __METHOD__ );
- return $ok;
- }
-
- /**
- * @since 1.21
- *
- * @return ORMTable
- */
- protected function newSitesTable() {
- return new ORMTable(
- 'sites',
- array(
- 'id' => 'id',
-
- // Site data
- 'global_key' => 'str',
- 'type' => 'str',
- 'group' => 'str',
- 'source' => 'str',
- 'language' => 'str',
- 'protocol' => 'str',
- 'domain' => 'str',
- 'data' => 'array',
-
- // Site config
- 'forward' => 'bool',
- 'config' => 'array',
- ),
- array(
- 'type' => Site::TYPE_UNKNOWN,
- 'group' => Site::GROUP_NONE,
- 'source' => Site::SOURCE_LOCAL,
- 'data' => array(),
-
- 'forward' => false,
- 'config' => array(),
- 'language' => '',
- ),
- 'ORMRow',
- 'site_'
- );
- }
-
-}
-
-/**
- * @deprecated since 1.21
- */
-class Sites extends SiteSQLStore {
-
- /**
- * Factory for creating new site objects.
- *
- * @since 1.21
- * @deprecated since 1.21
- *
- * @param string|bool $globalId
- *
- * @return Site
- */
- public static function newSite( $globalId = false ) {
- $site = new Site();
-
- if ( $globalId !== false ) {
- $site->setGlobalId( $globalId );
- }
-
- return $site;
- }
-
- /**
- * @deprecated since 1.21
- * @return SiteStore
- */
- public static function singleton() {
- static $singleton;
-
- if ( $singleton === null ) {
- $singleton = new static();
- }
-
- return $singleton;
- }
-
- /**
- * @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 537f1ccb..10e0c1b9 100644
--- a/includes/site/SiteStore.php
+++ b/includes/site/SiteStore.php
@@ -26,7 +26,7 @@
* @license GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-interface SiteStore {
+interface SiteStore extends SiteLookup {
/**
* Saves the provided site.
@@ -51,33 +51,6 @@ interface SiteStore {
public function saveSites( array $sites );
/**
- * Returns the site with provided global id, or null if there is no such site.
- *
- * @since 1.21
- *
- * @param string $globalId
- * @param string $source Either 'cache' or 'recache'.
- * If 'cache', the values are allowed (but not obliged) to come from a cache.
- *
- * @return Site|null
- */
- public function getSite( $globalId, $source = 'cache' );
-
- /**
- * Returns a list of all sites. By default this site is
- * fetched from the cache, which can be changed to loading
- * the list from the database using the $useCache parameter.
- *
- * @since 1.21
- *
- * @param string $source Either 'cache' or 'recache'.
- * If 'cache', the values are allowed (but not obliged) to come from a cache.
- *
- * @return SiteList
- */
- public function getSites( $source = 'cache' );
-
- /**
* Deletes all sites from the database. After calling clear(), getSites() will return an empty
* list and getSite() will return null until saveSite() or saveSites() is called.
*/
diff --git a/includes/site/SitesCacheFileBuilder.php b/includes/site/SitesCacheFileBuilder.php
new file mode 100644
index 00000000..2e420409
--- /dev/null
+++ b/includes/site/SitesCacheFileBuilder.php
@@ -0,0 +1,113 @@
+<?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
+ *
+ * @since 1.25
+ *
+ * @file
+ *
+ * @license GNU GPL v2+
+ */
+class SitesCacheFileBuilder {
+
+ /**
+ * @var SiteLookup
+ */
+ private $siteLookup;
+
+ /**
+ * @var string
+ */
+ private $cacheFile;
+
+ /**
+ * @param SiteLookup $siteLookup
+ * @param string $cacheFile
+ */
+ public function __construct( SiteLookup $siteLookup, $cacheFile ) {
+ $this->siteLookup = $siteLookup;
+ $this->cacheFile = $cacheFile;
+ }
+
+ public function build() {
+ $this->sites = $this->siteLookup->getSites();
+ $this->cacheSites( $this->sites->getArrayCopy() );
+ }
+
+ /**
+ * @param Site[] $sites
+ *
+ * @throws MWException if in manualRecache mode
+ * @return bool
+ */
+ private function cacheSites( array $sites ) {
+ $sitesArray = array();
+
+ foreach ( $sites as $site ) {
+ $globalId = $site->getGlobalId();
+ $sitesArray[$globalId] = $this->getSiteAsArray( $site );
+ }
+
+ $json = json_encode( array(
+ 'sites' => $sitesArray
+ ) );
+
+ $result = file_put_contents( $this->cacheFile, $json );
+
+ return $result !== false;
+ }
+
+ /**
+ * @param Site $site
+ *
+ * @return array
+ */
+ private function getSiteAsArray( Site $site ) {
+ $siteEntry = unserialize( $site->serialize() );
+ $siteIdentifiers = $this->buildLocalIdentifiers( $site );
+ $identifiersArray = array();
+
+ foreach ( $siteIdentifiers as $identifier ) {
+ $identifiersArray[] = $identifier;
+ }
+
+ $siteEntry['identifiers'] = $identifiersArray;
+
+ return $siteEntry;
+ }
+
+ /**
+ * @param Site $site
+ *
+ * @return array Site local identifiers
+ */
+ private function buildLocalIdentifiers( Site $site ) {
+ $localIds = array();
+
+ foreach ( $site->getLocalIds() as $idType => $ids ) {
+ foreach ( $ids as $id ) {
+ $localIds[] = array(
+ 'type' => $idType,
+ 'key' => $id
+ );
+ }
+ }
+
+ return $localIds;
+ }
+
+}
diff --git a/includes/skins/BaseTemplate.php b/includes/skins/BaseTemplate.php
new file mode 100644
index 00000000..25df0f93
--- /dev/null
+++ b/includes/skins/BaseTemplate.php
@@ -0,0 +1,644 @@
+<?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
+ */
+
+/**
+ * New base template for a skin's template extended from QuickTemplate
+ * this class features helper methods that provide common ways of interacting
+ * with the data stored in the QuickTemplate
+ */
+abstract class BaseTemplate extends QuickTemplate {
+
+ /**
+ * Get a Message object with its context set
+ *
+ * @param string $name Message name
+ * @return Message
+ */
+ public function getMsg( $name ) {
+ return $this->getSkin()->msg( $name );
+ }
+
+ function msg( $str ) {
+ echo $this->getMsg( $str )->escaped();
+ }
+
+ function msgHtml( $str ) {
+ echo $this->getMsg( $str )->text();
+ }
+
+ function msgWiki( $str ) {
+ echo $this->getMsg( $str )->parseAsBlock();
+ }
+
+ /**
+ * Create an array of common toolbox items from the data in the quicktemplate
+ * stored by SkinTemplate.
+ * The resulting array is built according to a format intended to be passed
+ * through makeListItem to generate the html.
+ * @return array
+ */
+ function getToolbox() {
+
+ $toolbox = array();
+ 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']
+ ) {
+ $toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
+ $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
+ $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
+ }
+ if ( isset( $this->data['feeds'] ) && $this->data['feeds'] ) {
+ $toolbox['feeds']['id'] = 'feedlinks';
+ $toolbox['feeds']['links'] = array();
+ foreach ( $this->data['feeds'] as $key => $feed ) {
+ $toolbox['feeds']['links'][$key] = $feed;
+ $toolbox['feeds']['links'][$key]['id'] = "feed-$key";
+ $toolbox['feeds']['links'][$key]['rel'] = 'alternate';
+ $toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
+ $toolbox['feeds']['links'][$key]['class'] = 'feedlink';
+ }
+ }
+ 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";
+ }
+ }
+ if ( isset( $this->data['nav_urls']['print'] ) && $this->data['nav_urls']['print'] ) {
+ $toolbox['print'] = $this->data['nav_urls']['print'];
+ $toolbox['print']['id'] = 't-print';
+ $toolbox['print']['rel'] = 'alternate';
+ $toolbox['print']['msg'] = 'printableversion';
+ }
+ if ( isset( $this->data['nav_urls']['permalink'] ) && $this->data['nav_urls']['permalink'] ) {
+ $toolbox['permalink'] = $this->data['nav_urls']['permalink'];
+ if ( $toolbox['permalink']['href'] === '' ) {
+ unset( $toolbox['permalink']['href'] );
+ $toolbox['ispermalink']['tooltiponly'] = true;
+ $toolbox['ispermalink']['id'] = 't-ispermalink';
+ $toolbox['ispermalink']['msg'] = 'permalink';
+ } else {
+ $toolbox['permalink']['id'] = 't-permalink';
+ }
+ }
+ if ( isset( $this->data['nav_urls']['info'] ) && $this->data['nav_urls']['info'] ) {
+ $toolbox['info'] = $this->data['nav_urls']['info'];
+ $toolbox['info']['id'] = 't-info';
+ }
+
+ Hooks::run( 'BaseTemplateToolbox', array( &$this, &$toolbox ) );
+ return $toolbox;
+ }
+
+ /**
+ * Create an array of personal tools items from the data in the quicktemplate
+ * stored by SkinTemplate.
+ * The resulting array is built according to a format intended to be passed
+ * through makeListItem to generate the html.
+ * This is in reality the same list as already stored in personal_urls
+ * however it is reformatted so that you can just pass the individual items
+ * to makeListItem instead of hardcoding the element creation boilerplate.
+ * @return array
+ */
+ function getPersonalTools() {
+ $personal_tools = array();
+ foreach ( $this->get( 'personal_urls' ) as $key => $plink ) {
+ # The class on a personal_urls item is meant to go on the <a> instead
+ # of the <li> so we have to use a single item "links" array instead
+ # of using most of the personal_url's keys directly.
+ $ptool = array(
+ 'links' => array(
+ array( 'single-id' => "pt-$key" ),
+ ),
+ 'id' => "pt-$key",
+ );
+ if ( isset( $plink['active'] ) ) {
+ $ptool['active'] = $plink['active'];
+ }
+ foreach ( array( 'href', 'class', 'text', 'dir' ) as $k ) {
+ if ( isset( $plink[$k] ) ) {
+ $ptool['links'][0][$k] = $plink[$k];
+ }
+ }
+ $personal_tools[$key] = $ptool;
+ }
+ return $personal_tools;
+ }
+
+ function getSidebar( $options = array() ) {
+ // Force the rendering of the following portals
+ $sidebar = $this->data['sidebar'];
+ if ( !isset( $sidebar['SEARCH'] ) ) {
+ $sidebar['SEARCH'] = true;
+ }
+ if ( !isset( $sidebar['TOOLBOX'] ) ) {
+ $sidebar['TOOLBOX'] = true;
+ }
+ if ( !isset( $sidebar['LANGUAGES'] ) ) {
+ $sidebar['LANGUAGES'] = true;
+ }
+
+ if ( !isset( $options['search'] ) || $options['search'] !== true ) {
+ unset( $sidebar['SEARCH'] );
+ }
+ if ( isset( $options['toolbox'] ) && $options['toolbox'] === false ) {
+ unset( $sidebar['TOOLBOX'] );
+ }
+ if ( isset( $options['languages'] ) && $options['languages'] === false ) {
+ unset( $sidebar['LANGUAGES'] );
+ }
+
+ $boxes = array();
+ foreach ( $sidebar as $boxName => $content ) {
+ if ( $content === false ) {
+ continue;
+ }
+ switch ( $boxName ) {
+ case 'SEARCH':
+ // Search is a special case, skins should custom implement this
+ $boxes[$boxName] = array(
+ 'id' => 'p-search',
+ 'header' => $this->getMsg( 'search' )->text(),
+ 'generated' => false,
+ 'content' => true,
+ );
+ break;
+ case 'TOOLBOX':
+ $msgObj = $this->getMsg( 'toolbox' );
+ $boxes[$boxName] = array(
+ 'id' => 'p-tb',
+ 'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
+ 'generated' => false,
+ 'content' => $this->getToolbox(),
+ );
+ break;
+ case 'LANGUAGES':
+ if ( $this->data['language_urls'] ) {
+ $msgObj = $this->getMsg( 'otherlanguages' );
+ $boxes[$boxName] = array(
+ 'id' => 'p-lang',
+ 'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
+ 'generated' => false,
+ 'content' => $this->data['language_urls'],
+ );
+ }
+ break;
+ default:
+ $msgObj = $this->getMsg( $boxName );
+ $boxes[$boxName] = array(
+ 'id' => "p-$boxName",
+ 'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
+ 'generated' => true,
+ 'content' => $content,
+ );
+ break;
+ }
+ }
+
+ // HACK: Compatibility with extensions still using SkinTemplateToolboxEnd
+ $hookContents = null;
+ if ( isset( $boxes['TOOLBOX'] ) ) {
+ ob_start();
+ // We pass an extra 'true' at the end so extensions using BaseTemplateToolbox
+ // can abort and avoid outputting double toolbox links
+ Hooks::run( 'SkinTemplateToolboxEnd', array( &$this, true ) );
+ $hookContents = ob_get_contents();
+ ob_end_clean();
+ if ( !trim( $hookContents ) ) {
+ $hookContents = null;
+ }
+ }
+ // END hack
+
+ if ( isset( $options['htmlOnly'] ) && $options['htmlOnly'] === true ) {
+ foreach ( $boxes as $boxName => $box ) {
+ if ( is_array( $box['content'] ) ) {
+ $content = '<ul>';
+ foreach ( $box['content'] as $key => $val ) {
+ $content .= "\n " . $this->makeListItem( $key, $val );
+ }
+ // HACK, shove the toolbox end onto the toolbox if we're rendering itself
+ if ( $hookContents ) {
+ $content .= "\n $hookContents";
+ }
+ // END hack
+ $content .= "\n</ul>\n";
+ $boxes[$boxName]['content'] = $content;
+ }
+ }
+ } else {
+ if ( $hookContents ) {
+ $boxes['TOOLBOXEND'] = array(
+ 'id' => 'p-toolboxend',
+ 'header' => $boxes['TOOLBOX']['header'],
+ 'generated' => false,
+ 'content' => "<ul>{$hookContents}</ul>",
+ );
+ // HACK: Make sure that TOOLBOXEND is sorted next to TOOLBOX
+ $boxes2 = array();
+ foreach ( $boxes as $key => $box ) {
+ if ( $key === 'TOOLBOXEND' ) {
+ continue;
+ }
+ $boxes2[$key] = $box;
+ if ( $key === 'TOOLBOX' ) {
+ $boxes2['TOOLBOXEND'] = $boxes['TOOLBOXEND'];
+ }
+ }
+ $boxes = $boxes2;
+ // END hack
+ }
+ }
+
+ return $boxes;
+ }
+
+ /**
+ * @param string $name
+ */
+ protected function renderAfterPortlet( $name ) {
+ $content = '';
+ Hooks::run( '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
+ * link from.
+ * @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
+ * that name will be used, and if neither of those are set the $key will be
+ * used as a message name.
+ *
+ * If a "href" key is not present makeLink will just output htmlescaped text.
+ * The "href", "id", "class", "rel", and "type" keys are used as attributes
+ * for the link if present.
+ *
+ * If an "id" or "single-id" (if you don't want the actual id to be output
+ * on the link) is present it will be used to generate a tooltip and
+ * accesskey for the link.
+ *
+ * The keys "context" and "primary" are ignored; these keys are used
+ * internally by skins and are not supposed to be included in the HTML
+ * output.
+ *
+ * If you don't want an accesskey, set $item['tooltiponly'] = true;
+ *
+ * @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
+ * optionally an 'attributes' key. If you only have one element you don't
+ * need to wrap it in another array. eg: To use <a><span>...</span></a>
+ * in all links use array( 'text-wrapper' => array( 'tag' => 'span' ) )
+ * for your options.
+ * - 'link-class' key can be used to specify additional classes to apply
+ * to all links.
+ * - 'link-fallback' can be used to specify a tag to use instead of "<a>"
+ * if there is no link. eg: If you specify 'link-fallback' => 'span' than
+ * any non-link will output a "<span>" instead of just text.
+ *
+ * @return string
+ */
+ function makeLink( $key, $item, $options = array() ) {
+ if ( isset( $item['text'] ) ) {
+ $text = $item['text'];
+ } else {
+ $text = $this->translator->translate( isset( $item['msg'] ) ? $item['msg'] : $key );
+ }
+
+ $html = htmlspecialchars( $text );
+
+ if ( isset( $options['text-wrapper'] ) ) {
+ $wrapper = $options['text-wrapper'];
+ if ( isset( $wrapper['tag'] ) ) {
+ $wrapper = array( $wrapper );
+ }
+ while ( count( $wrapper ) > 0 ) {
+ $element = array_pop( $wrapper );
+ $html = Html::rawElement( $element['tag'], isset( $element['attributes'] )
+ ? $element['attributes']
+ : null, $html );
+ }
+ }
+
+ if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
+ $attrs = $item;
+ foreach ( array( 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary' ) as $k ) {
+ unset( $attrs[$k] );
+ }
+
+ if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
+ $item['single-id'] = $item['id'];
+ }
+ if ( isset( $item['single-id'] ) ) {
+ if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
+ $title = Linker::titleAttrib( $item['single-id'] );
+ if ( $title !== false ) {
+ $attrs['title'] = $title;
+ }
+ } else {
+ $tip = Linker::tooltipAndAccesskeyAttribs( $item['single-id'] );
+ if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
+ $attrs['title'] = $tip['title'];
+ }
+ if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
+ $attrs['accesskey'] = $tip['accesskey'];
+ }
+ }
+ }
+ if ( isset( $options['link-class'] ) ) {
+ if ( isset( $attrs['class'] ) ) {
+ $attrs['class'] .= " {$options['link-class']}";
+ } else {
+ $attrs['class'] = $options['link-class'];
+ }
+ }
+ $html = Html::rawElement( isset( $attrs['href'] )
+ ? 'a'
+ : $options['link-fallback'], $attrs, $html );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generates a list item for a navigation, portlet, portal, sidebar... list
+ *
+ * @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 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.
+ * link/content data for the list item may come in one of two forms
+ * A "links" key may be used, in which case it should contain an array with
+ * a list of links to include inside the list item, see makeLink for the
+ * format of individual links array items.
+ *
+ * Otherwise the relevant keys from the list item $item array will be passed
+ * to makeLink instead. Note however that "id" and "class" are used by the
+ * 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. 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
+ */
+ function makeListItem( $key, $item, $options = array() ) {
+ if ( isset( $item['links'] ) ) {
+ $links = array();
+ foreach ( $item['links'] as $linkKey => $link ) {
+ $links[] = $this->makeLink( $linkKey, $link, $options );
+ }
+ $html = implode( ' ', $links );
+ } else {
+ $link = $item;
+ // These keys are used by makeListItem and shouldn't be passed on to the link
+ foreach ( array( 'id', 'class', 'active', 'tag', 'itemtitle' ) as $k ) {
+ unset( $link[$k] );
+ }
+ if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
+ // The id goes on the <li> not on the <a> for single links
+ // but makeSidebarLink still needs to know what id to use when
+ // generating tooltips and accesskeys.
+ $link['single-id'] = $item['id'];
+ }
+ $html = $this->makeLink( $key, $link, $options );
+ }
+
+ $attrs = array();
+ foreach ( array( 'id', 'class' ) as $attr ) {
+ if ( isset( $item[$attr] ) ) {
+ $attrs[$attr] = $item[$attr];
+ }
+ }
+ if ( isset( $item['active'] ) && $item['active'] ) {
+ if ( !isset( $attrs['class'] ) ) {
+ $attrs['class'] = '';
+ }
+ $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 );
+ }
+
+ function makeSearchInput( $attrs = array() ) {
+ $realAttrs = array(
+ 'type' => 'search',
+ 'name' => 'search',
+ 'placeholder' => wfMessage( 'searchsuggest-search' )->text(),
+ 'value' => $this->get( 'search', '' ),
+ );
+ $realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
+ return Html::element( 'input', $realAttrs );
+ }
+
+ function makeSearchButton( $mode, $attrs = array() ) {
+ switch ( $mode ) {
+ case 'go':
+ case 'fulltext':
+ $realAttrs = array(
+ 'type' => 'submit',
+ 'name' => $mode,
+ 'value' => $this->translator->translate(
+ $mode == 'go' ? 'searcharticle' : 'searchbutton' ),
+ );
+ $realAttrs = array_merge(
+ $realAttrs,
+ Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
+ $attrs
+ );
+ return Html::element( 'input', $realAttrs );
+ case 'image':
+ $buttonAttrs = array(
+ 'type' => 'submit',
+ 'name' => 'button',
+ );
+ $buttonAttrs = array_merge(
+ $buttonAttrs,
+ Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
+ $attrs
+ );
+ unset( $buttonAttrs['src'] );
+ unset( $buttonAttrs['alt'] );
+ unset( $buttonAttrs['width'] );
+ unset( $buttonAttrs['height'] );
+ $imgAttrs = array(
+ 'src' => $attrs['src'],
+ 'alt' => isset( $attrs['alt'] )
+ ? $attrs['alt']
+ : $this->translator->translate( 'searchbutton' ),
+ 'width' => isset( $attrs['width'] ) ? $attrs['width'] : null,
+ 'height' => isset( $attrs['height'] ) ? $attrs['height'] : null,
+ );
+ return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
+ default:
+ throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
+ }
+ }
+
+ /**
+ * Returns an array of footerlinks trimmed down to only those footer links that
+ * are valid.
+ * 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 ) {
+ $footerlinks = $this->get( 'footerlinks' );
+
+ // Reduce footer links down to only those which are being used
+ $validFooterLinks = array();
+ foreach ( $footerlinks as $category => $links ) {
+ $validFooterLinks[$category] = array();
+ foreach ( $links as $link ) {
+ if ( isset( $this->data[$link] ) && $this->data[$link] ) {
+ $validFooterLinks[$category][] = $link;
+ }
+ }
+ if ( count( $validFooterLinks[$category] ) <= 0 ) {
+ unset( $validFooterLinks[$category] );
+ }
+ }
+
+ if ( $option == 'flat' ) {
+ // fold footerlinks into a single array using a bit of trickery
+ $validFooterLinks = call_user_func_array(
+ 'array_merge',
+ array_values( $validFooterLinks )
+ );
+ }
+
+ return $validFooterLinks;
+ }
+
+ /**
+ * Returns an array of footer icons filtered down by options relevant to how
+ * the skin wishes to display them.
+ * If you pass "icononly" as the option all footer icons which do not have an
+ * image icon set will be filtered out.
+ * If you pass "nocopyright" then MediaWiki's copyright icon will not be included
+ * 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.
+ * @param string $option
+ * @return string
+ */
+ function getFooterIcons( $option = null ) {
+ // Generate additional footer icons
+ $footericons = $this->get( 'footericons' );
+
+ if ( $option == 'icononly' ) {
+ // Unset any icons which don't have an image
+ foreach ( $footericons as &$footerIconsBlock ) {
+ foreach ( $footerIconsBlock as $footerIconKey => $footerIcon ) {
+ if ( !is_string( $footerIcon ) && !isset( $footerIcon['src'] ) ) {
+ unset( $footerIconsBlock[$footerIconKey] );
+ }
+ }
+ }
+ // Redo removal of any empty blocks
+ foreach ( $footericons as $footerIconsKey => &$footerIconsBlock ) {
+ if ( count( $footerIconsBlock ) <= 0 ) {
+ unset( $footericons[$footerIconsKey] );
+ }
+ }
+ } elseif ( $option == 'nocopyright' ) {
+ unset( $footericons['copyright']['copyright'] );
+ if ( count( $footericons['copyright'] ) <= 0 ) {
+ unset( $footericons['copyright'] );
+ }
+ }
+
+ return $footericons;
+ }
+
+ /**
+ * Get the suggested HTML for page status indicators: icons (or short text snippets) usually
+ * displayed in the top-right corner of the page, outside of the main content.
+ *
+ * Your skin may implement this differently, for example by handling some indicator names
+ * specially with a different UI. However, it is recommended to use a `<div class="mw-indicator"
+ * id="mw-indicator-<id>" />` as a wrapper element for each indicator, for better compatibility
+ * with extensions and user scripts.
+ *
+ * The raw data is available in `$this->data['indicators']` as an associative array (keys:
+ * identifiers, values: contents) internally ordered by keys.
+ *
+ * @return string HTML
+ * @since 1.25
+ */
+ public function getIndicators() {
+ $out = "<div class=\"mw-indicators\">\n";
+ foreach ( $this->data['indicators'] as $id => $content ) {
+ $out .= Html::rawElement(
+ 'div',
+ array(
+ 'id' => Sanitizer::escapeId( "mw-indicator-$id" ),
+ 'class' => 'mw-indicator',
+ ),
+ $content
+ ) . "\n";
+ }
+ $out .= "</div>\n";
+ return $out;
+ }
+
+ /**
+ * Output the basic end-page trail including bottomscripts, reporttime, and
+ * debug stuff. This should be called right before outputting the closing
+ * body and html tags.
+ */
+ function printTrail() {
+?>
+<?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() ); ?>
+<?php $this->html( 'bottomscripts' ); /* JS call to runBodyOnloadHook */ ?>
+<?php $this->html( 'reporttime' ) ?>
+<?php
+ }
+}
diff --git a/includes/skins/MediaWikiI18N.php b/includes/skins/MediaWikiI18N.php
new file mode 100644
index 00000000..6e48d04a
--- /dev/null
+++ b/includes/skins/MediaWikiI18N.php
@@ -0,0 +1,52 @@
+<?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
+ */
+
+/**
+ * Wrapper object for MediaWiki's localization functions,
+ * to be passed to the template engine.
+ *
+ * @private
+ * @ingroup Skins
+ */
+class MediaWikiI18N {
+ private $context = array();
+
+ function set( $varName, $value ) {
+ $this->context[$varName] = $value;
+ }
+
+ function translate( $value ) {
+
+ // Hack for i18n:attributes in PHPTAL 1.0.0 dev version as of 2004-10-23
+ $value = preg_replace( '/^string:/', '', $value );
+
+ $value = wfMessage( $value )->text();
+ // interpolate variables
+ $m = array();
+ while ( preg_match( '/\$([0-9]*?)/sm', $value, $m ) ) {
+ list( $src, $var ) = $m;
+ wfSuppressWarnings();
+ $varValue = $this->context[$var];
+ wfRestoreWarnings();
+ $value = str_replace( $src, $varValue, $value );
+ }
+ return $value;
+ }
+}
diff --git a/includes/skins/QuickTemplate.php b/includes/skins/QuickTemplate.php
new file mode 100644
index 00000000..905e537e
--- /dev/null
+++ b/includes/skins/QuickTemplate.php
@@ -0,0 +1,194 @@
+<?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
+ */
+
+/**
+ * Generic wrapper for template functions, with interface
+ * compatible with what we use of PHPTAL 0.7.
+ * @ingroup Skins
+ */
+abstract class QuickTemplate {
+
+ /** @var Config $config */
+ protected $config;
+
+ /**
+ * @param Config $config
+ */
+ function __construct( Config $config = null ) {
+ $this->data = array();
+ $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 string $name
+ * @param mixed $value
+ */
+ public function set( $name, $value ) {
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * extends the value of data with name $name with the value $value
+ * @since 1.25
+ * @param string $name
+ * @param mixed $value
+ */
+ public function extend( $name, $value ) {
+ if ( $this->haveData( $name ) ) {
+ $this->data[$name] = $this->data[$name] . $value;
+ } else {
+ $this->data[$name] = $value;
+ }
+ }
+
+ /**
+ * Gets the template data requested
+ * @since 1.22
+ * @param string $name Key for the data
+ * @param mixed $default Optional default (or null)
+ * @return mixed The value of the data requested or the deafult
+ */
+ public function get( $name, $default = null ) {
+ if ( isset( $this->data[$name] ) ) {
+ return $this->data[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setRef( $name, &$value ) {
+ $this->data[$name] =& $value;
+ }
+
+ /**
+ * @param MediaWikiI18N $t
+ */
+ public function setTranslator( &$t ) {
+ $this->translator = &$t;
+ }
+
+ /**
+ * Main function, used by classes that subclass QuickTemplate
+ * to show the actual HTML output
+ */
+ abstract public function execute();
+
+ /**
+ * @private
+ * @param string $str
+ * @return string
+ */
+ function text( $str ) {
+ echo htmlspecialchars( $this->data[$str] );
+ }
+
+ /**
+ * @private
+ * @param string $str
+ * @return string
+ */
+ function html( $str ) {
+ echo $this->data[$str];
+ }
+
+ /**
+ * @private
+ * @param string $str
+ * @return string
+ */
+ function msg( $str ) {
+ echo htmlspecialchars( $this->translator->translate( $str ) );
+ }
+
+ /**
+ * @private
+ * @param string $str
+ * @return string
+ */
+ function msgHtml( $str ) {
+ echo $this->translator->translate( $str );
+ }
+
+ /**
+ * An ugly, ugly hack.
+ * @private
+ * @param string $str
+ * @return string
+ */
+ function msgWiki( $str ) {
+ global $wgOut;
+
+ $text = $this->translator->translate( $str );
+ echo $wgOut->parse( $text );
+ }
+
+ /**
+ * @private
+ * @param string $str
+ * @return bool
+ */
+ function haveData( $str ) {
+ return isset( $this->data[$str] );
+ }
+
+ /**
+ * @private
+ *
+ * @param string $str
+ * @return bool
+ */
+ function haveMsg( $str ) {
+ $msg = $this->translator->translate( $str );
+ return ( $msg != '-' ) && ( $msg != '' ); # ????
+ }
+
+ /**
+ * Get the Skin object related to this 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;
+ }
+}
diff --git a/includes/skins/Skin.php b/includes/skins/Skin.php
index 2f6a7101..ac7a85ba 100644
--- a/includes/skins/Skin.php
+++ b/includes/skins/Skin.php
@@ -168,11 +168,9 @@ abstract class Skin extends ContextSource {
* @param OutputPage $out
*/
function initPage( OutputPage $out ) {
- wfProfileIn( __METHOD__ );
$this->preloadExistence();
- wfProfileOut( __METHOD__ );
}
/**
@@ -239,25 +237,33 @@ abstract class Skin extends ContextSource {
* Preload the existence of three commonly-requested pages in a single query
*/
function preloadExistence() {
+ $titles = array();
+
$user = $this->getUser();
+ $title = $this->getRelevantTitle();
// User/talk link
- $titles = array( $user->getUserPage(), $user->getTalkPage() );
+ if ( $user->isLoggedIn() || $this->showIPinHeader() ) {
+ $titles[] = $user->getUserPage();
+ $titles[] = $user->getTalkPage();
+ }
// Other tab link
- if ( $this->getTitle()->isSpecialPage() ) {
+ if ( $title->isSpecialPage() ) {
// nothing
- } elseif ( $this->getTitle()->isTalkPage() ) {
- $titles[] = $this->getTitle()->getSubjectPage();
+ } elseif ( $title->isTalkPage() ) {
+ $titles[] = $title->getSubjectPage();
} else {
- $titles[] = $this->getTitle()->getTalkPage();
+ $titles[] = $title->getTalkPage();
}
- wfRunHooks( 'SkinPreloadExistence', array( &$titles, $this ) );
+ Hooks::run( 'SkinPreloadExistence', array( &$titles, $this ) );
- $lb = new LinkBatch( $titles );
- $lb->setCaller( __METHOD__ );
- $lb->execute();
+ if ( count( $titles ) ) {
+ $lb = new LinkBatch( $titles );
+ $lb->setCaller( __METHOD__ );
+ $lb->execute();
+ }
}
/**
@@ -333,8 +339,13 @@ abstract class Skin extends ContextSource {
$this->mRelevantUser = User::newFromName( $rootUser, false );
} else {
$user = User::newFromName( $rootUser, false );
- if ( $user && $user->isLoggedIn() ) {
- $this->mRelevantUser = $user;
+
+ if ( $user ) {
+ $user->load( User::READ_NORMAL );
+
+ if ( $user->isLoggedIn() ) {
+ $this->mRelevantUser = $user;
+ }
}
}
return $this->mRelevantUser;
@@ -474,9 +485,10 @@ abstract class Skin extends ContextSource {
$msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped();
$linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
+ $title = Title::newFromText( $linkPage );
+ $link = $title ? Linker::link( $title, $msg ) : $msg;
$s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
- Linker::link( Title::newFromText( $linkPage ), $msg )
- . $colon . '<ul>' . $t . '</ul>' . '</div>';
+ $link . $colon . '<ul>' . $t . '</ul>' . '</div>';
}
# Hidden categories
@@ -579,7 +591,7 @@ abstract class Skin extends ContextSource {
protected function afterContentHook() {
$data = '';
- if ( wfRunHooks( 'SkinAfterContent', array( &$data, $this ) ) ) {
+ if ( Hooks::run( 'SkinAfterContent', array( &$data, $this ) ) ) {
// adding just some spaces shouldn't toggle the output
// of the whole <div/>, so we use trim() here
if ( trim( $data ) != '' ) {
@@ -616,7 +628,7 @@ abstract class Skin extends ContextSource {
// OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
// up at some point
$bottomScriptText = $this->getOutput()->getBottomScripts();
- wfRunHooks( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) );
+ Hooks::run( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) );
return $bottomScriptText;
}
@@ -637,22 +649,23 @@ abstract class Skin extends ContextSource {
$url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL() ) );
}
- return $this->msg( 'retrievedfrom', '<a dir="ltr" href="' . $url
- . '">' . $url . '</a>' )->text();
+ return $this->msg( 'retrievedfrom' )
+ ->rawParams( '<a dir="ltr" href="' . $url. '">' . $url . '</a>' )
+ ->parse();
}
/**
- * @return string
+ * @return string HTML
*/
function getUndeleteLink() {
$action = $this->getRequest()->getVal( 'action', 'view' );
- if ( $this->getUser()->isAllowed( 'deletedhistory' ) &&
- ( $this->getTitle()->getArticleID() == 0 || $action == 'history' ) ) {
+ if ( $this->getTitle()->userCan( 'deletedhistory', $this->getUser() ) &&
+ ( !$this->getTitle()->exists() || $action == 'history' ) ) {
$n = $this->getTitle()->isDeleted();
if ( $n ) {
- if ( $this->getUser()->isAllowed( 'undelete' ) ) {
+ if ( $this->getTitle()->quickUserCan( 'undelete', $this->getUser() ) ) {
$msg = 'thisisdeleted';
} else {
$msg = 'viewdeleted';
@@ -662,7 +675,7 @@ abstract class Skin extends ContextSource {
Linker::linkKnown(
SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ),
$this->msg( 'restorelink' )->numParams( $n )->escaped() )
- )->text();
+ )->escaped();
}
}
@@ -676,7 +689,7 @@ abstract class Skin extends ContextSource {
$out = $this->getOutput();
$subpages = '';
- if ( !wfRunHooks( 'SkinSubPageSubtitle', array( &$subpages, $this, $out ) ) ) {
+ if ( !Hooks::run( 'SkinSubPageSubtitle', array( &$subpages, $this, $out ) ) ) {
return $subpages;
}
@@ -785,7 +798,7 @@ abstract class Skin extends ContextSource {
// @todo Remove deprecated $forContent param from hook handlers and then remove here.
$forContent = true;
- wfRunHooks(
+ Hooks::run(
'SkinCopyrightFooter',
array( $this->getTitle(), $type, &$msg, &$link, &$forContent )
);
@@ -829,10 +842,19 @@ abstract class Skin extends ContextSource {
function getPoweredBy() {
global $wgResourceBasePath;
- $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 ) );
+ $url1 = htmlspecialchars(
+ "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
+ );
+ $url1_5 = htmlspecialchars(
+ "$wgResourceBasePath/resources/assets/poweredby_mediawiki_132x47.png"
+ );
+ $url2 = htmlspecialchars(
+ "$wgResourceBasePath/resources/assets/poweredby_mediawiki_176x62.png"
+ );
+ $text = '<a href="//www.mediawiki.org/"><img src="' . $url1
+ . '" srcset="' . $url1_5 . ' 1.5x, ' . $url2 . ' 2x" '
+ . 'height="31" width="88" alt="Powered by MediaWiki" /></a>';
+ Hooks::run( 'SkinGetPoweredBy', array( &$text, $this ) );
return $text;
}
@@ -852,13 +874,13 @@ abstract class Skin extends ContextSource {
if ( $timestamp ) {
$d = $this->getLanguage()->userDate( $timestamp, $this->getUser() );
$t = $this->getLanguage()->userTime( $timestamp, $this->getUser() );
- $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->text();
+ $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->parse();
} else {
$s = '';
}
if ( wfGetLB()->getLaggedSlaveMode() ) {
- $s .= ' <strong>' . $this->msg( 'laggedslavemode' )->text() . '</strong>';
+ $s .= ' <strong>' . $this->msg( 'laggedslavemode' )->parse() . '</strong>';
}
return $s;
@@ -942,6 +964,10 @@ abstract class Skin extends ContextSource {
// but we make the link target be the one site-wide page.
$title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
+ if ( !$title ) {
+ return '';
+ }
+
return Linker::linkKnown(
$title,
$this->msg( $desc )->escaped()
@@ -1031,6 +1057,7 @@ abstract class Skin extends ContextSource {
*
* @param string $name The name or path of a skin resource file
* @return string The fully resolved style path url including styleversion
+ * @throws MWException
*/
function getSkinStylePath( $name ) {
global $wgStylePath, $wgStyleVersion;
@@ -1150,7 +1177,7 @@ abstract class Skin extends ContextSource {
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0,
+ 'exists' => $title->isKnown(),
);
}
@@ -1208,16 +1235,14 @@ abstract class Skin extends ContextSource {
*/
function buildSidebar() {
global $wgMemc, $wgEnableSidebarCache, $wgSidebarCacheExpiry;
- wfProfileIn( __METHOD__ );
$key = wfMemcKey( 'sidebar', $this->getLanguage()->getCode() );
if ( $wgEnableSidebarCache ) {
$cachedsidebar = $wgMemc->get( $key );
if ( $cachedsidebar ) {
- wfRunHooks( 'SidebarBeforeOutput', array( $this, &$cachedsidebar ) );
+ Hooks::run( 'SidebarBeforeOutput', array( $this, &$cachedsidebar ) );
- wfProfileOut( __METHOD__ );
return $cachedsidebar;
}
}
@@ -1225,14 +1250,13 @@ abstract class Skin extends ContextSource {
$bar = array();
$this->addToSidebar( $bar, 'sidebar' );
- wfRunHooks( 'SkinBuildSidebar', array( $this, &$bar ) );
+ Hooks::run( 'SkinBuildSidebar', array( $this, &$bar ) );
if ( $wgEnableSidebarCache ) {
$wgMemc->set( $key, $bar, $wgSidebarCacheExpiry );
}
- wfRunHooks( 'SidebarBeforeOutput', array( $this, &$bar ) );
+ Hooks::run( 'SidebarBeforeOutput', array( $this, &$bar ) );
- wfProfileOut( __METHOD__ );
return $bar;
}
@@ -1370,7 +1394,7 @@ abstract class Skin extends ContextSource {
$out = $this->getOutput();
// Allow extensions to disable or modify the new messages alert
- if ( !wfRunHooks( 'GetNewMessagesAlert', array( &$newMessagesAlert, $newtalks, $user, $out ) ) ) {
+ if ( !Hooks::run( 'GetNewMessagesAlert', array( &$newMessagesAlert, $newtalks, $user, $out ) ) ) {
return '';
}
if ( $newMessagesAlert ) {
@@ -1453,13 +1477,12 @@ abstract class Skin extends ContextSource {
* Get a cached notice
*
* @param string $name Message name, or 'default' for $wgSiteNotice
- * @return string HTML fragment
+ * @return string|bool HTML fragment, or false to indicate that the caller
+ * should fall back to the next notice in its sequence
*/
private function getCachedNotice( $name ) {
global $wgRenderHashAppend, $parserMemc, $wgContLang;
- wfProfileIn( __METHOD__ );
-
$needParse = false;
if ( $name === 'default' ) {
@@ -1467,13 +1490,13 @@ abstract class Skin extends ContextSource {
global $wgSiteNotice;
$notice = $wgSiteNotice;
if ( empty( $notice ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
} else {
$msg = $this->msg( $name )->inContentLanguage();
- if ( $msg->isDisabled() ) {
- wfProfileOut( __METHOD__ );
+ if ( $msg->isBlank() ) {
+ return '';
+ } elseif ( $msg->isDisabled() ) {
return false;
}
$notice = $msg->plain();
@@ -1500,7 +1523,6 @@ abstract class Skin extends ContextSource {
$notice = Html::rawElement( 'div', array( 'id' => 'localNotice',
'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $notice );
- wfProfileOut( __METHOD__ );
return $notice;
}
@@ -1510,7 +1532,6 @@ abstract class Skin extends ContextSource {
* @return string HTML fragment
*/
function getNamespaceNotice() {
- wfProfileIn( __METHOD__ );
$key = 'namespacenotice-' . $this->getTitle()->getNsText();
$namespaceNotice = $this->getCachedNotice( $key );
@@ -1520,7 +1541,6 @@ abstract class Skin extends ContextSource {
$namespaceNotice = '';
}
- wfProfileOut( __METHOD__ );
return $namespaceNotice;
}
@@ -1530,27 +1550,25 @@ abstract class Skin extends ContextSource {
* @return string HTML fragment
*/
function getSiteNotice() {
- wfProfileIn( __METHOD__ );
$siteNotice = '';
- if ( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice, $this ) ) ) {
+ if ( Hooks::run( 'SiteNoticeBefore', array( &$siteNotice, $this ) ) ) {
if ( is_object( $this->getUser() ) && $this->getUser()->isLoggedIn() ) {
$siteNotice = $this->getCachedNotice( 'sitenotice' );
} else {
$anonNotice = $this->getCachedNotice( 'anonnotice' );
- if ( !$anonNotice ) {
+ if ( $anonNotice === false ) {
$siteNotice = $this->getCachedNotice( 'sitenotice' );
} else {
$siteNotice = $anonNotice;
}
}
- if ( !$siteNotice ) {
+ if ( $siteNotice === false ) {
$siteNotice = $this->getCachedNotice( 'default' );
}
}
- wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice, $this ) );
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'SiteNoticeAfter', array( &$siteNotice, $this ) );
return $siteNotice;
}
@@ -1580,20 +1598,46 @@ abstract class Skin extends ContextSource {
$attribs['title'] = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
->inLanguage( $lang )->text();
}
- $link = Linker::link( $nt, wfMessage( 'editsection' )->inLanguage( $lang )->text(),
- $attribs,
- array( 'action' => 'edit', 'section' => $section ),
- array( 'noclasses', 'known' )
+
+ $links = array(
+ 'editsection' => array(
+ 'text' => wfMessage( 'editsection' )->inLanguage( $lang )->escaped(),
+ 'targetTitle' => $nt,
+ 'attribs' => $attribs,
+ 'query' => array( 'action' => 'edit', 'section' => $section ),
+ 'options' => array( 'noclasses', 'known' )
+ )
);
- # Add the brackets and the span and run the hook.
- $result = '<span class="mw-editsection">'
- . '<span class="mw-editsection-bracket">[</span>'
- . $link
- . '<span class="mw-editsection-bracket">]</span>'
- . '</span>';
+ Hooks::run( 'SkinEditSectionLinks', array( $this, $nt, $section, $tooltip, &$links, $lang ) );
+
+ $result = '<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>';
- wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
+ $linksHtml = array();
+ foreach ( $links as $k => $linkDetails ) {
+ $linksHtml[] = Linker::link(
+ $linkDetails['targetTitle'],
+ $linkDetails['text'],
+ $linkDetails['attribs'],
+ $linkDetails['query'],
+ $linkDetails['options']
+ );
+ }
+
+ $result .= implode(
+ '<span class="mw-editsection-divider">'
+ . wfMessage( 'pipe-separator' )->inLanguage( $lang )->text()
+ . '</span>',
+ $linksHtml
+ );
+
+ $result .= '<span class="mw-editsection-bracket">]</span></span>';
+ // Deprecated, use SkinEditSectionLinks hook instead
+ Hooks::run(
+ 'DoEditSectionLink',
+ array( $this, $nt, $section, $tooltip, &$result, $lang ),
+ '1.25'
+ );
return $result;
}
diff --git a/includes/skins/SkinApi.php b/includes/skins/SkinApi.php
new file mode 100644
index 00000000..2fef2209
--- /dev/null
+++ b/includes/skins/SkinApi.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Extremely basic "skin" for API output, which needs to output a page without
+ * the usual skin elements but still using CSS, JS, and such via OutputPage and
+ * ResourceLoader.
+ *
+ * Created on Sep 08, 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
+ */
+
+/**
+ * SkinTemplate class for API output
+ * @since 1.25
+ */
+class SkinApi extends SkinTemplate {
+ public $skinname = 'apioutput';
+ public $template = 'SkinApiTemplate';
+
+ public function setupSkinUserCss( OutputPage $out ) {
+ parent::setupSkinUserCss( $out );
+ $out->addModuleStyles( 'mediawiki.skinning.interface' );
+ }
+
+ // Skip work and hooks for stuff we don't use
+
+ function buildSidebar() {
+ return array();
+ }
+
+ function getNewtalks() {
+ return '';
+ }
+
+ function getSiteNotice() {
+ return '';
+ }
+
+ public function getLanguages() {
+ return array();
+ }
+
+ protected function buildPersonalUrls() {
+ return array();
+ }
+
+ protected function buildContentNavigationUrls() {
+ return array();
+ }
+
+ protected function buildNavUrls() {
+ return array();
+ }
+}
diff --git a/includes/skins/SkinApiTemplate.php b/includes/skins/SkinApiTemplate.php
new file mode 100644
index 00000000..97b70382
--- /dev/null
+++ b/includes/skins/SkinApiTemplate.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Extremely basic "skin" for API output, which needs to output a page without
+ * the usual skin elements but still using CSS, JS, and such via OutputPage and
+ * ResourceLoader.
+ *
+ * Created on Sep 08, 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
+ */
+
+/**
+ * BaseTemplate class for the 'apioutput' skin
+ * @since 1.25
+ */
+class SkinApiTemplate extends BaseTemplate {
+
+ public function execute() {
+ $this->html( 'headelement' ) ?>
+
+ <div class="mw-body" role="main">
+ <h1 class="firstHeading"><?php $this->html( 'title' ) ?></h1>
+ <div class="mw-body-content">
+ <?php $this->html( 'bodytext' ) ?>
+ </div>
+ </div>
+
+ <?php $this->printTrail() ?>
+ </body></html>
+<?php
+ }
+
+ // Skip work and hooks for stuff we don't use
+
+ function getToolbox() {
+ return array();
+ }
+
+ function getPersonalTools() {
+ return array();
+ }
+
+ function getSidebar( $options = array() ) {
+ return array();
+ }
+}
diff --git a/includes/skins/SkinFactory.php b/includes/skins/SkinFactory.php
index fb408577..ffbe6293 100644
--- a/includes/skins/SkinFactory.php
+++ b/includes/skins/SkinFactory.php
@@ -40,13 +40,6 @@ class SkinFactory {
* @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
@@ -83,109 +76,13 @@ class SkinFactory {
}
/**
- * @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;
-
+ return $this->displayNames;
}
/**
@@ -197,11 +94,6 @@ class SkinFactory {
*/
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 );
diff --git a/includes/skins/SkinFallbackTemplate.php b/includes/skins/SkinFallbackTemplate.php
index 603ee5c5..1c5f3a6f 100644
--- a/includes/skins/SkinFallbackTemplate.php
+++ b/includes/skins/SkinFallbackTemplate.php
@@ -17,16 +17,18 @@ class SkinFallbackTemplate extends BaseTemplate {
* @return array
*/
private function findInstalledSkins() {
- $styleDirectory = $this->config->get( 'StyleDirectory' ); // @todo we should inject this directly?
+ $styleDirectory = $this->config->get( 'StyleDirectory' );
// 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
+ // Filter out skins that aren't installed
$possibleSkins = array_filter( $possibleSkins, function ( $skinDir ) use ( $styleDirectory ) {
- return is_file( "$styleDirectory/$skinDir/$skinDir.php" );
+ return
+ is_file( "$styleDirectory/$skinDir/skin.json" )
+ || is_file( "$styleDirectory/$skinDir/$skinDir.php" );
} );
return $possibleSkins;
@@ -56,7 +58,7 @@ class SkinFallbackTemplate extends BaseTemplate {
} else {
$skinsInstalledText[] = $this->getMsg( 'default-skin-not-found-row-disabled' )
->params( $normalizedKey, $skin )->plain();
- $skinsInstalledSnippet[] = "require_once \"\$IP/skins/$skin/$skin.php\";";
+ $skinsInstalledSnippet[] = $this->getSnippetForSkin( $skin );
}
}
@@ -73,6 +75,21 @@ class SkinFallbackTemplate extends BaseTemplate {
}
/**
+ * Get the appropriate LocalSettings.php snippet to enable the given skin
+ *
+ * @param string $skin
+ * @return string
+ */
+ private function getSnippetForSkin( $skin ) {
+ global $IP;
+ if ( file_exists( "$IP/skins/$skin/skin.json" ) ) {
+ return "wfLoadSkin( '$skin' );";
+ } else {
+ return "require_once \"\$IP/skins/$skin/$skin.php\";";
+ }
+ }
+
+ /**
* Outputs the entire contents of the page. No navigation (other than search box), just the big
* warning message and page content.
*/
@@ -91,9 +108,7 @@ class SkinFallbackTemplate extends BaseTemplate {
</form>
<div class="mw-body" role="main">
- <h1 class="firstHeading">
- <span dir="auto"><?php $this->html( 'title' ) ?></span>
- </h1>
+ <h1 class="firstHeading"><?php $this->html( 'title' ) ?></h1>
<div class="mw-body-content">
<?php $this->html( 'bodytext' ) ?>
diff --git a/includes/skins/SkinTemplate.php b/includes/skins/SkinTemplate.php
index b66862b8..61aad921 100644
--- a/includes/skins/SkinTemplate.php
+++ b/includes/skins/SkinTemplate.php
@@ -1,7 +1,5 @@
<?php
/**
- * Base class for template-based skins.
- *
* This 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
@@ -21,41 +19,8 @@
*/
/**
- * Wrapper object for MediaWiki's localization functions,
- * to be passed to the template engine.
+ * Base class for template-based skins.
*
- * @private
- * @ingroup Skins
- */
-class MediaWikiI18N {
- private $context = array();
-
- function set( $varName, $value ) {
- $this->context[$varName] = $value;
- }
-
- function translate( $value ) {
- wfProfileIn( __METHOD__ );
-
- // Hack for i18n:attributes in PHPTAL 1.0.0 dev version as of 2004-10-23
- $value = preg_replace( '/^string:/', '', $value );
-
- $value = wfMessage( $value )->text();
- // interpolate variables
- $m = array();
- while ( preg_match( '/\$([0-9]*?)/sm', $value, $m ) ) {
- list( $src, $var ) = $m;
- wfSuppressWarnings();
- $varValue = $this->context[$var];
- wfRestoreWarnings();
- $value = str_replace( $src, $varValue, $value );
- }
- wfProfileOut( __METHOD__ );
- return $value;
- }
-}
-
-/**
* Template-filler skin base class
* Formerly generic PHPTal (http://phptal.sourceforge.net/) skin
* Based on Brion's smarty skin
@@ -89,7 +54,8 @@ class SkinTemplate extends Skin {
$out->addModuleStyles( array(
'mediawiki.legacy.shared',
'mediawiki.legacy.commonPrint',
- 'mediawiki.ui.button'
+ 'mediawiki.ui.button',
+ 'mediawiki.sectionAnchor'
) );
}
@@ -198,7 +164,7 @@ class SkinTemplate extends Skin {
'lang' => $ilInterwikiCodeBCP47,
'hreflang' => $ilInterwikiCodeBCP47,
);
- wfRunHooks(
+ Hooks::run(
'SkinTemplateGetLanguageLink',
array( &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() )
);
@@ -210,17 +176,13 @@ class SkinTemplate extends Skin {
}
protected function setupTemplateForOutput() {
- wfProfileIn( __METHOD__ );
$request = $this->getRequest();
$user = $this->getUser();
$title = $this->getTitle();
- wfProfileIn( __METHOD__ . '-init' );
$tpl = $this->setupTemplate( $this->template, 'skins' );
- wfProfileOut( __METHOD__ . '-init' );
- wfProfileIn( __METHOD__ . '-stuff' );
$this->thispage = $title->getPrefixedDBkey();
$this->titletxt = $title->getPrefixedText();
$this->userpage = $user->getUserPage()->getPrefixedText();
@@ -243,10 +205,6 @@ class SkinTemplate extends Skin {
$this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage );
}
- wfProfileOut( __METHOD__ . '-stuff' );
-
- wfProfileOut( __METHOD__ );
-
return $tpl;
}
@@ -256,26 +214,22 @@ class SkinTemplate extends Skin {
* @param OutputPage $out
*/
function outputPage( OutputPage $out = null ) {
- wfProfileIn( __METHOD__ );
Profiler::instance()->setTemplated( true );
$oldContext = null;
if ( $out !== null ) {
- // @todo Add wfDeprecated in 1.20
+ // Deprecated since 1.20, note added in 1.25
+ wfDeprecated( __METHOD__, '1.25' );
$oldContext = $this->getContext();
$this->setContext( $out->getContext() );
}
$out = $this->getOutput();
- wfProfileIn( __METHOD__ . '-init' );
$this->initPage( $out );
- wfProfileOut( __METHOD__ . '-init' );
$tpl = $this->prepareQuickTemplate( $out );
// execute template
- wfProfileIn( __METHOD__ . '-execute' );
$res = $tpl->execute();
- wfProfileOut( __METHOD__ . '-execute' );
// result may be an error
$this->printOrError( $res );
@@ -284,7 +238,6 @@ class SkinTemplate extends Skin {
$this->setContext( $oldContext );
}
- wfProfileOut( __METHOD__ );
}
/**
@@ -295,18 +248,15 @@ class SkinTemplate extends Skin {
*/
protected function prepareQuickTemplate() {
global $wgContLang, $wgScript, $wgStylePath, $wgMimeType, $wgJsMimeType,
- $wgDisableCounters, $wgSitename, $wgLogo, $wgMaxCredits,
- $wgShowCreditsIfMax, $wgPageShowWatchingUsers, $wgArticlePath,
+ $wgSitename, $wgLogo, $wgMaxCredits,
+ $wgShowCreditsIfMax, $wgArticlePath,
$wgScriptPath, $wgServer;
- wfProfileIn( __METHOD__ );
-
$title = $this->getTitle();
$request = $this->getRequest();
$out = $this->getOutput();
$tpl = $this->setupTemplateForOutput();
- wfProfileIn( __METHOD__ . '-stuff2' );
$tpl->set( 'title', $out->getPageTitle() );
$tpl->set( 'pagetitle', $out->getHTMLTitle() );
$tpl->set( 'displaytitle', $out->mPageLinkTitle );
@@ -401,39 +351,17 @@ class SkinTemplate extends Skin {
$tpl->set( 'userlangattributes', $attrs );
}
- wfProfileOut( __METHOD__ . '-stuff2' );
-
- wfProfileIn( __METHOD__ . '-stuff3' );
$tpl->set( 'newtalk', $this->getNewtalks() );
$tpl->set( 'logo', $this->logoText() );
$tpl->set( 'copyright', false );
+ // No longer used
$tpl->set( 'viewcount', false );
$tpl->set( 'lastmod', false );
$tpl->set( 'credits', false );
$tpl->set( 'numberofwatchingusers', false );
if ( $out->isArticle() && $title->exists() ) {
if ( $this->isRevisionCurrent() ) {
- if ( !$wgDisableCounters ) {
- $viewcount = $this->getWikiPage()->getCount();
- if ( $viewcount ) {
- $tpl->set( 'viewcount', $this->msg( 'viewcount' )->numParams( $viewcount )->parse() );
- }
- }
-
- if ( $wgPageShowWatchingUsers ) {
- $dbr = wfGetDB( DB_SLAVE );
- $num = $dbr->selectField( 'watchlist', 'COUNT(*)',
- array( 'wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace() ),
- __METHOD__
- );
- if ( $num > 0 ) {
- $tpl->set( 'numberofwatchingusers',
- $this->msg( 'number_of_watching_users_pageview' )->numParams( $num )->parse()
- );
- }
- }
-
if ( $wgMaxCredits != 0 ) {
$tpl->set( 'credits', Action::factory( 'credits', $this->getWikiPage(),
$this->getContext() )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) );
@@ -443,9 +371,7 @@ class SkinTemplate extends Skin {
}
$tpl->set( 'copyright', $this->getCopyright() );
}
- wfProfileOut( __METHOD__ . '-stuff3' );
- wfProfileIn( __METHOD__ . '-stuff4' );
$tpl->set( 'copyrightico', $this->getCopyrightIcon() );
$tpl->set( 'poweredbyico', $this->getPoweredBy() );
$tpl->set( 'disclaimer', $this->disclaimerLink() );
@@ -455,7 +381,6 @@ class SkinTemplate extends Skin {
$tpl->set( 'footerlinks', array(
'info' => array(
'lastmod',
- 'viewcount',
'numberofwatchingusers',
'credits',
'copyright',
@@ -486,6 +411,8 @@ class SkinTemplate extends Skin {
}
}
+ $tpl->set( 'indicators', $out->getIndicators() );
+
$tpl->set( 'sitenotice', $this->getSiteNotice() );
$tpl->set( 'bottomscripts', $this->bottomScripts() );
$tpl->set( 'printfooter', $this->printSource() );
@@ -515,9 +442,7 @@ class SkinTemplate extends Skin {
} else {
$tpl->set( 'language_urls', false );
}
- wfProfileOut( __METHOD__ . '-stuff4' );
- wfProfileIn( __METHOD__ . '-stuff5' );
# Personal toolbar
$tpl->set( 'personal_urls', $this->buildPersonalUrls() );
$content_navigation = $this->buildContentNavigationUrls();
@@ -536,11 +461,11 @@ class SkinTemplate extends Skin {
$tpl->set( 'reporttime', wfReportTime() );
// original version by hansm
- if ( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
+ if ( !Hooks::run( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" );
}
- // Set the bodytext to another key so that skins can just output it on it's own
+ // Set the bodytext to another key so that skins can just output it on its own
// and output printfooter and debughtml separately
$tpl->set( 'bodycontent', $tpl->data['bodytext'] );
@@ -557,9 +482,7 @@ class SkinTemplate extends Skin {
// allow extensions adding stuff after the page content.
// See Skin::afterContentHook() for further documentation.
$tpl->set( 'dataAfterContent', $this->afterContentHook() );
- wfProfileOut( __METHOD__ . '-stuff5' );
- wfProfileOut( __METHOD__ );
return $tpl;
}
@@ -623,7 +546,6 @@ class SkinTemplate extends Skin {
$title = $this->getTitle();
$request = $this->getRequest();
$pageurl = $title->getLocalURL();
- wfProfileIn( __METHOD__ );
/* set up the default links for the personal toolbar */
$personal_urls = array();
@@ -755,8 +677,7 @@ class SkinTemplate extends Skin {
$personal_urls['login'] = $login_url;
}
- wfRunHooks( 'PersonalUrls', array( &$personal_urls, &$title, $this ) );
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'PersonalUrls', array( &$personal_urls, &$title, $this ) );
return $personal_urls;
}
@@ -796,12 +717,12 @@ class SkinTemplate extends Skin {
$text = $msg->text();
} else {
global $wgContLang;
- $text = $wgContLang->getFormattedNsText(
+ $text = $wgContLang->getConverter()->convertNamespace(
MWNamespace::getSubject( $title->getNamespace() ) );
}
$result = array();
- if ( !wfRunHooks( 'SkinTemplateTabAction', array( &$this,
+ if ( !Hooks::run( 'SkinTemplateTabAction', array( &$this,
$title, $message, $selected, $checkEdit,
&$classes, &$query, &$text, &$result ) ) ) {
return $result;
@@ -823,17 +744,20 @@ class SkinTemplate extends Skin {
self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0,
+ 'exists' => $title->isKnown(),
);
}
+ /**
+ * @todo is this even used?
+ */
function makeArticleUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
$title = $title->getSubjectPage();
self::checkTitle( $title, $name );
return array(
'href' => $title->getLocalURL( $urlaction ),
- 'exists' => $title->getArticleID() != 0,
+ 'exists' => $title->exists(),
);
}
@@ -874,8 +798,6 @@ class SkinTemplate extends Skin {
protected function buildContentNavigationUrls() {
global $wgDisableLangConversion;
- wfProfileIn( __METHOD__ );
-
// Display tabs for the relevant title rather than always the title itself
$title = $this->getRelevantTitle();
$onPage = $title->equals( $this->getTitle() );
@@ -897,7 +819,7 @@ class SkinTemplate extends Skin {
$userCanRead = $title->quickUserCan( 'read', $user );
$preventActiveTabs = false;
- wfRunHooks( 'SkinTemplatePreventOtherActiveTabs', array( &$this, &$preventActiveTabs ) );
+ Hooks::run( 'SkinTemplatePreventOtherActiveTabs', array( &$this, &$preventActiveTabs ) );
// Checks if page is some kind of content
if ( $title->canExist() ) {
@@ -961,8 +883,6 @@ class SkinTemplate extends Skin {
);
}
- 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 ) )
@@ -1019,9 +939,7 @@ class SkinTemplate extends Skin {
'primary' => true, // don't collapse this in vector
);
}
- wfProfileOut( __METHOD__ . '-edit' );
- wfProfileIn( __METHOD__ . '-live' );
// Checks if the page exists
if ( $title->exists() ) {
// Adds history view link
@@ -1030,7 +948,6 @@ class SkinTemplate extends Skin {
'text' => wfMessageFallback( "$skname-view-history", 'history_short' )
->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( 'action=history' ),
- 'rel' => 'archives',
);
if ( $title->quickUserCan( 'delete', $user ) ) {
@@ -1056,7 +973,7 @@ class SkinTemplate extends Skin {
if ( $user->isAllowed( 'deletedhistory' ) ) {
$n = $title->isDeleted();
if ( $n ) {
- $undelTitle = SpecialPage::getTitleFor( 'Undelete' );
+ $undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() );
// If the user can't undelete but can view deleted
// history show them a "View .. deleted" tab instead.
$msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
@@ -1064,7 +981,7 @@ class SkinTemplate extends Skin {
'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
->setContext( $this->getContext() )->numParams( $n )->text(),
- 'href' => $undelTitle->getLocalURL( array( 'target' => $title->getPrefixedDBkey() ) )
+ 'href' => $undelTitle->getLocalURL()
);
}
}
@@ -1082,8 +999,6 @@ class SkinTemplate extends Skin {
);
}
- wfProfileOut( __METHOD__ . '-live' );
-
// Checks if the user is logged in
if ( $this->loggedin && $user->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ) ) {
/**
@@ -1106,7 +1021,7 @@ class SkinTemplate extends Skin {
}
}
- wfRunHooks( 'SkinTemplateNavigation', array( &$this, &$content_navigation ) );
+ Hooks::run( 'SkinTemplateNavigation', array( &$this, &$content_navigation ) );
if ( $userCanRead && !$wgDisableLangConversion ) {
$pageLang = $title->getPageLanguage();
@@ -1148,12 +1063,12 @@ class SkinTemplate extends Skin {
'context' => 'subject'
);
- wfRunHooks( 'SkinTemplateNavigation::SpecialPage',
+ Hooks::run( 'SkinTemplateNavigation::SpecialPage',
array( &$this, &$content_navigation ) );
}
// Equiv to SkinTemplateContentActions
- wfRunHooks( 'SkinTemplateNavigation::Universal', array( &$this, &$content_navigation ) );
+ Hooks::run( 'SkinTemplateNavigation::Universal', array( &$this, &$content_navigation ) );
// Setup xml ids and tooltip info
foreach ( $content_navigation as $section => &$links ) {
@@ -1190,8 +1105,6 @@ class SkinTemplate extends Skin {
}
}
- wfProfileOut( __METHOD__ );
-
return $content_navigation;
}
@@ -1202,8 +1115,6 @@ class SkinTemplate extends Skin {
*/
private function buildContentActionUrls( $content_navigation ) {
- wfProfileIn( __METHOD__ );
-
// content_actions has been replaced with content_navigation for backwards
// compatibility and also for skins that just want simple tabs content_actions
// is now built by flattening the content_navigation arrays into one
@@ -1235,8 +1146,6 @@ class SkinTemplate extends Skin {
}
}
- wfProfileOut( __METHOD__ );
-
return $content_actions;
}
@@ -1247,8 +1156,6 @@ class SkinTemplate extends Skin {
protected function buildNavUrls() {
global $wgUploadNavigationUrl;
- wfProfileIn( __METHOD__ );
-
$out = $this->getOutput();
$request = $this->getRequest();
@@ -1295,7 +1202,7 @@ class SkinTemplate extends Skin {
}
// Use the copy of revision ID in case this undocumented, shady hook tries to mess with internals
- wfRunHooks( 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink',
+ Hooks::run( 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink',
array( &$this, &$nav_urls, &$revid, &$revid ) );
}
@@ -1309,7 +1216,7 @@ class SkinTemplate extends Skin {
'href' => $this->getTitle()->getLocalURL( "action=info" )
);
- if ( $this->getTitle()->getArticleID() ) {
+ if ( $this->getTitle()->exists() ) {
$nav_urls['recentchangeslinked'] = array(
'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalURL()
);
@@ -1353,7 +1260,6 @@ class SkinTemplate extends Skin {
}
}
- wfProfileOut( __METHOD__ );
return $nav_urls;
}
@@ -1365,759 +1271,3 @@ class SkinTemplate extends Skin {
return $this->getTitle()->getNamespaceKey();
}
}
-
-/**
- * Generic wrapper for template functions, with interface
- * compatible with what we use of PHPTAL 0.7.
- * @ingroup Skins
- */
-abstract class QuickTemplate {
-
- /** @var Config $config */
- protected $config;
-
- /**
- * @param Config $config
- */
- function __construct( Config $config = null ) {
- $this->data = array();
- $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 string $name
- * @param mixed $value
- */
- public function set( $name, $value ) {
- $this->data[$name] = $value;
- }
-
- /**
- * Gets the template data requested
- * @since 1.22
- * @param string $name Key for the data
- * @param mixed $default Optional default (or null)
- * @return mixed The value of the data requested or the deafult
- */
- public function get( $name, $default = null ) {
- if ( isset( $this->data[$name] ) ) {
- return $this->data[$name];
- } else {
- return $default;
- }
- }
-
- /**
- * @param string $name
- * @param mixed $value
- */
- public function setRef( $name, &$value ) {
- $this->data[$name] =& $value;
- }
-
- /**
- * @param MediaWikiI18N $t
- */
- public function setTranslator( &$t ) {
- $this->translator = &$t;
- }
-
- /**
- * Main function, used by classes that subclass QuickTemplate
- * to show the actual HTML output
- */
- abstract public function execute();
-
- /**
- * @private
- * @param string $str
- * @return string
- */
- function text( $str ) {
- echo htmlspecialchars( $this->data[$str] );
- }
-
- /**
- * @private
- * @param string $str
- * @return string
- */
- function html( $str ) {
- echo $this->data[$str];
- }
-
- /**
- * @private
- * @param string $str
- * @return string
- */
- function msg( $str ) {
- echo htmlspecialchars( $this->translator->translate( $str ) );
- }
-
- /**
- * @private
- * @param string $str
- * @return string
- */
- function msgHtml( $str ) {
- echo $this->translator->translate( $str );
- }
-
- /**
- * An ugly, ugly hack.
- * @private
- * @param string $str
- * @return string
- */
- function msgWiki( $str ) {
- global $wgOut;
-
- $text = $this->translator->translate( $str );
- echo $wgOut->parse( $text );
- }
-
- /**
- * @private
- * @param string $str
- * @return bool
- */
- function haveData( $str ) {
- return isset( $this->data[$str] );
- }
-
- /**
- * @private
- *
- * @param string $str
- * @return bool
- */
- function haveMsg( $str ) {
- $msg = $this->translator->translate( $str );
- return ( $msg != '-' ) && ( $msg != '' ); # ????
- }
-
- /**
- * Get the Skin object related to this 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;
- }
-}
-
-/**
- * New base template for a skin's template extended from QuickTemplate
- * this class features helper methods that provide common ways of interacting
- * with the data stored in the QuickTemplate
- */
-abstract class BaseTemplate extends QuickTemplate {
-
- /**
- * Get a Message object with its context set
- *
- * @param string $name Message name
- * @return Message
- */
- public function getMsg( $name ) {
- return $this->getSkin()->msg( $name );
- }
-
- function msg( $str ) {
- echo $this->getMsg( $str )->escaped();
- }
-
- function msgHtml( $str ) {
- echo $this->getMsg( $str )->text();
- }
-
- function msgWiki( $str ) {
- echo $this->getMsg( $str )->parseAsBlock();
- }
-
- /**
- * Create an array of common toolbox items from the data in the quicktemplate
- * stored by SkinTemplate.
- * The resulting array is built according to a format intended to be passed
- * through makeListItem to generate the html.
- * @return array
- */
- function getToolbox() {
- wfProfileIn( __METHOD__ );
-
- $toolbox = array();
- 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']
- ) {
- $toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
- $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
- $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
- }
- if ( isset( $this->data['feeds'] ) && $this->data['feeds'] ) {
- $toolbox['feeds']['id'] = 'feedlinks';
- $toolbox['feeds']['links'] = array();
- foreach ( $this->data['feeds'] as $key => $feed ) {
- $toolbox['feeds']['links'][$key] = $feed;
- $toolbox['feeds']['links'][$key]['id'] = "feed-$key";
- $toolbox['feeds']['links'][$key]['rel'] = 'alternate';
- $toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
- $toolbox['feeds']['links'][$key]['class'] = 'feedlink';
- }
- }
- 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";
- }
- }
- if ( isset( $this->data['nav_urls']['print'] ) && $this->data['nav_urls']['print'] ) {
- $toolbox['print'] = $this->data['nav_urls']['print'];
- $toolbox['print']['id'] = 't-print';
- $toolbox['print']['rel'] = 'alternate';
- $toolbox['print']['msg'] = 'printableversion';
- }
- if ( isset( $this->data['nav_urls']['permalink'] ) && $this->data['nav_urls']['permalink'] ) {
- $toolbox['permalink'] = $this->data['nav_urls']['permalink'];
- if ( $toolbox['permalink']['href'] === '' ) {
- unset( $toolbox['permalink']['href'] );
- $toolbox['ispermalink']['tooltiponly'] = true;
- $toolbox['ispermalink']['id'] = 't-ispermalink';
- $toolbox['ispermalink']['msg'] = 'permalink';
- } else {
- $toolbox['permalink']['id'] = 't-permalink';
- }
- }
- if ( isset( $this->data['nav_urls']['info'] ) && $this->data['nav_urls']['info'] ) {
- $toolbox['info'] = $this->data['nav_urls']['info'];
- $toolbox['info']['id'] = 't-info';
- }
-
- wfRunHooks( 'BaseTemplateToolbox', array( &$this, &$toolbox ) );
- wfProfileOut( __METHOD__ );
- return $toolbox;
- }
-
- /**
- * Create an array of personal tools items from the data in the quicktemplate
- * stored by SkinTemplate.
- * The resulting array is built according to a format intended to be passed
- * through makeListItem to generate the html.
- * This is in reality the same list as already stored in personal_urls
- * however it is reformatted so that you can just pass the individual items
- * to makeListItem instead of hardcoding the element creation boilerplate.
- * @return array
- */
- function getPersonalTools() {
- $personal_tools = array();
- foreach ( $this->get( 'personal_urls' ) as $key => $plink ) {
- # The class on a personal_urls item is meant to go on the <a> instead
- # of the <li> so we have to use a single item "links" array instead
- # of using most of the personal_url's keys directly.
- $ptool = array(
- 'links' => array(
- array( 'single-id' => "pt-$key" ),
- ),
- 'id' => "pt-$key",
- );
- if ( isset( $plink['active'] ) ) {
- $ptool['active'] = $plink['active'];
- }
- foreach ( array( 'href', 'class', 'text', 'dir' ) as $k ) {
- if ( isset( $plink[$k] ) ) {
- $ptool['links'][0][$k] = $plink[$k];
- }
- }
- $personal_tools[$key] = $ptool;
- }
- return $personal_tools;
- }
-
- function getSidebar( $options = array() ) {
- // Force the rendering of the following portals
- $sidebar = $this->data['sidebar'];
- if ( !isset( $sidebar['SEARCH'] ) ) {
- $sidebar['SEARCH'] = true;
- }
- if ( !isset( $sidebar['TOOLBOX'] ) ) {
- $sidebar['TOOLBOX'] = true;
- }
- if ( !isset( $sidebar['LANGUAGES'] ) ) {
- $sidebar['LANGUAGES'] = true;
- }
-
- if ( !isset( $options['search'] ) || $options['search'] !== true ) {
- unset( $sidebar['SEARCH'] );
- }
- if ( isset( $options['toolbox'] ) && $options['toolbox'] === false ) {
- unset( $sidebar['TOOLBOX'] );
- }
- if ( isset( $options['languages'] ) && $options['languages'] === false ) {
- unset( $sidebar['LANGUAGES'] );
- }
-
- $boxes = array();
- foreach ( $sidebar as $boxName => $content ) {
- if ( $content === false ) {
- continue;
- }
- switch ( $boxName ) {
- case 'SEARCH':
- // Search is a special case, skins should custom implement this
- $boxes[$boxName] = array(
- 'id' => 'p-search',
- 'header' => $this->getMsg( 'search' )->text(),
- 'generated' => false,
- 'content' => true,
- );
- break;
- case 'TOOLBOX':
- $msgObj = $this->getMsg( 'toolbox' );
- $boxes[$boxName] = array(
- 'id' => 'p-tb',
- 'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
- 'generated' => false,
- 'content' => $this->getToolbox(),
- );
- break;
- case 'LANGUAGES':
- if ( $this->data['language_urls'] ) {
- $msgObj = $this->getMsg( 'otherlanguages' );
- $boxes[$boxName] = array(
- 'id' => 'p-lang',
- 'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
- 'generated' => false,
- 'content' => $this->data['language_urls'],
- );
- }
- break;
- default:
- $msgObj = $this->getMsg( $boxName );
- $boxes[$boxName] = array(
- 'id' => "p-$boxName",
- 'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
- 'generated' => true,
- 'content' => $content,
- );
- break;
- }
- }
-
- // HACK: Compatibility with extensions still using SkinTemplateToolboxEnd
- $hookContents = null;
- if ( isset( $boxes['TOOLBOX'] ) ) {
- ob_start();
- // We pass an extra 'true' at the end so extensions using BaseTemplateToolbox
- // can abort and avoid outputting double toolbox links
- wfRunHooks( 'SkinTemplateToolboxEnd', array( &$this, true ) );
- $hookContents = ob_get_contents();
- ob_end_clean();
- if ( !trim( $hookContents ) ) {
- $hookContents = null;
- }
- }
- // END hack
-
- if ( isset( $options['htmlOnly'] ) && $options['htmlOnly'] === true ) {
- foreach ( $boxes as $boxName => $box ) {
- if ( is_array( $box['content'] ) ) {
- $content = '<ul>';
- foreach ( $box['content'] as $key => $val ) {
- $content .= "\n " . $this->makeListItem( $key, $val );
- }
- // HACK, shove the toolbox end onto the toolbox if we're rendering itself
- if ( $hookContents ) {
- $content .= "\n $hookContents";
- }
- // END hack
- $content .= "\n</ul>\n";
- $boxes[$boxName]['content'] = $content;
- }
- }
- } else {
- if ( $hookContents ) {
- $boxes['TOOLBOXEND'] = array(
- 'id' => 'p-toolboxend',
- 'header' => $boxes['TOOLBOX']['header'],
- 'generated' => false,
- 'content' => "<ul>{$hookContents}</ul>",
- );
- // HACK: Make sure that TOOLBOXEND is sorted next to TOOLBOX
- $boxes2 = array();
- foreach ( $boxes as $key => $box ) {
- if ( $key === 'TOOLBOXEND' ) {
- continue;
- }
- $boxes2[$key] = $box;
- if ( $key === 'TOOLBOX' ) {
- $boxes2['TOOLBOXEND'] = $boxes['TOOLBOXEND'];
- }
- }
- $boxes = $boxes2;
- // END hack
- }
- }
-
- return $boxes;
- }
-
- /**
- * @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
- * link from.
- * @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
- * that name will be used, and if neither of those are set the $key will be
- * used as a message name.
- *
- * If a "href" key is not present makeLink will just output htmlescaped text.
- * The "href", "id", "class", "rel", and "type" keys are used as attributes
- * for the link if present.
- *
- * If an "id" or "single-id" (if you don't want the actual id to be output
- * on the link) is present it will be used to generate a tooltip and
- * accesskey for the link.
- *
- * The keys "context" and "primary" are ignored; these keys are used
- * internally by skins and are not supposed to be included in the HTML
- * output.
- *
- * If you don't want an accesskey, set $item['tooltiponly'] = true;
- *
- * @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
- * optionally an 'attributes' key. If you only have one element you don't
- * need to wrap it in another array. eg: To use <a><span>...</span></a>
- * in all links use array( 'text-wrapper' => array( 'tag' => 'span' ) )
- * for your options.
- * - 'link-class' key can be used to specify additional classes to apply
- * to all links.
- * - 'link-fallback' can be used to specify a tag to use instead of "<a>"
- * if there is no link. eg: If you specify 'link-fallback' => 'span' than
- * any non-link will output a "<span>" instead of just text.
- *
- * @return string
- */
- function makeLink( $key, $item, $options = array() ) {
- if ( isset( $item['text'] ) ) {
- $text = $item['text'];
- } else {
- $text = $this->translator->translate( isset( $item['msg'] ) ? $item['msg'] : $key );
- }
-
- $html = htmlspecialchars( $text );
-
- if ( isset( $options['text-wrapper'] ) ) {
- $wrapper = $options['text-wrapper'];
- if ( isset( $wrapper['tag'] ) ) {
- $wrapper = array( $wrapper );
- }
- while ( count( $wrapper ) > 0 ) {
- $element = array_pop( $wrapper );
- $html = Html::rawElement( $element['tag'], isset( $element['attributes'] )
- ? $element['attributes']
- : null, $html );
- }
- }
-
- if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
- $attrs = $item;
- foreach ( array( 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary' ) as $k ) {
- unset( $attrs[$k] );
- }
-
- if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
- $item['single-id'] = $item['id'];
- }
- if ( isset( $item['single-id'] ) ) {
- if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
- $title = Linker::titleAttrib( $item['single-id'] );
- if ( $title !== false ) {
- $attrs['title'] = $title;
- }
- } else {
- $tip = Linker::tooltipAndAccesskeyAttribs( $item['single-id'] );
- if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
- $attrs['title'] = $tip['title'];
- }
- if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
- $attrs['accesskey'] = $tip['accesskey'];
- }
- }
- }
- if ( isset( $options['link-class'] ) ) {
- if ( isset( $attrs['class'] ) ) {
- $attrs['class'] .= " {$options['link-class']}";
- } else {
- $attrs['class'] = $options['link-class'];
- }
- }
- $html = Html::rawElement( isset( $attrs['href'] )
- ? 'a'
- : $options['link-fallback'], $attrs, $html );
- }
-
- return $html;
- }
-
- /**
- * Generates a list item for a navigation, portlet, portal, sidebar... list
- *
- * @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 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.
- * link/content data for the list item may come in one of two forms
- * A "links" key may be used, in which case it should contain an array with
- * a list of links to include inside the list item, see makeLink for the
- * format of individual links array items.
- *
- * Otherwise the relevant keys from the list item $item array will be passed
- * to makeLink instead. Note however that "id" and "class" are used by the
- * 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. 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
- */
- function makeListItem( $key, $item, $options = array() ) {
- if ( isset( $item['links'] ) ) {
- $links = array();
- foreach ( $item['links'] as $linkKey => $link ) {
- $links[] = $this->makeLink( $linkKey, $link, $options );
- }
- $html = implode( ' ', $links );
- } else {
- $link = $item;
- // These keys are used by makeListItem and shouldn't be passed on to the link
- foreach ( array( 'id', 'class', 'active', 'tag', 'itemtitle' ) as $k ) {
- unset( $link[$k] );
- }
- if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
- // The id goes on the <li> not on the <a> for single links
- // but makeSidebarLink still needs to know what id to use when
- // generating tooltips and accesskeys.
- $link['single-id'] = $item['id'];
- }
- $html = $this->makeLink( $key, $link, $options );
- }
-
- $attrs = array();
- foreach ( array( 'id', 'class' ) as $attr ) {
- if ( isset( $item[$attr] ) ) {
- $attrs[$attr] = $item[$attr];
- }
- }
- if ( isset( $item['active'] ) && $item['active'] ) {
- if ( !isset( $attrs['class'] ) ) {
- $attrs['class'] = '';
- }
- $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 );
- }
-
- function makeSearchInput( $attrs = array() ) {
- $realAttrs = array(
- 'type' => 'search',
- 'name' => 'search',
- 'placeholder' => wfMessage( 'searchsuggest-search' )->text(),
- 'value' => $this->get( 'search', '' ),
- );
- $realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
- return Html::element( 'input', $realAttrs );
- }
-
- function makeSearchButton( $mode, $attrs = array() ) {
- switch ( $mode ) {
- case 'go':
- case 'fulltext':
- $realAttrs = array(
- 'type' => 'submit',
- 'name' => $mode,
- 'value' => $this->translator->translate(
- $mode == 'go' ? 'searcharticle' : 'searchbutton' ),
- );
- $realAttrs = array_merge(
- $realAttrs,
- Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
- $attrs
- );
- return Html::element( 'input', $realAttrs );
- case 'image':
- $buttonAttrs = array(
- 'type' => 'submit',
- 'name' => 'button',
- );
- $buttonAttrs = array_merge(
- $buttonAttrs,
- Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
- $attrs
- );
- unset( $buttonAttrs['src'] );
- unset( $buttonAttrs['alt'] );
- unset( $buttonAttrs['width'] );
- unset( $buttonAttrs['height'] );
- $imgAttrs = array(
- 'src' => $attrs['src'],
- 'alt' => isset( $attrs['alt'] )
- ? $attrs['alt']
- : $this->translator->translate( 'searchbutton' ),
- 'width' => isset( $attrs['width'] ) ? $attrs['width'] : null,
- 'height' => isset( $attrs['height'] ) ? $attrs['height'] : null,
- );
- return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
- default:
- throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
- }
- }
-
- /**
- * Returns an array of footerlinks trimmed down to only those footer links that
- * are valid.
- * 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 ) {
- $footerlinks = $this->get( 'footerlinks' );
-
- // Reduce footer links down to only those which are being used
- $validFooterLinks = array();
- foreach ( $footerlinks as $category => $links ) {
- $validFooterLinks[$category] = array();
- foreach ( $links as $link ) {
- if ( isset( $this->data[$link] ) && $this->data[$link] ) {
- $validFooterLinks[$category][] = $link;
- }
- }
- if ( count( $validFooterLinks[$category] ) <= 0 ) {
- unset( $validFooterLinks[$category] );
- }
- }
-
- if ( $option == 'flat' ) {
- // fold footerlinks into a single array using a bit of trickery
- $validFooterLinks = call_user_func_array(
- 'array_merge',
- array_values( $validFooterLinks )
- );
- }
-
- return $validFooterLinks;
- }
-
- /**
- * Returns an array of footer icons filtered down by options relevant to how
- * the skin wishes to display them.
- * If you pass "icononly" as the option all footer icons which do not have an
- * image icon set will be filtered out.
- * If you pass "nocopyright" then MediaWiki's copyright icon will not be included
- * 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.
- * @param string $option
- * @return string
- */
- function getFooterIcons( $option = null ) {
- // Generate additional footer icons
- $footericons = $this->get( 'footericons' );
-
- if ( $option == 'icononly' ) {
- // Unset any icons which don't have an image
- foreach ( $footericons as &$footerIconsBlock ) {
- foreach ( $footerIconsBlock as $footerIconKey => $footerIcon ) {
- if ( !is_string( $footerIcon ) && !isset( $footerIcon['src'] ) ) {
- unset( $footerIconsBlock[$footerIconKey] );
- }
- }
- }
- // Redo removal of any empty blocks
- foreach ( $footericons as $footerIconsKey => &$footerIconsBlock ) {
- if ( count( $footerIconsBlock ) <= 0 ) {
- unset( $footericons[$footerIconsKey] );
- }
- }
- } elseif ( $option == 'nocopyright' ) {
- unset( $footericons['copyright']['copyright'] );
- if ( count( $footericons['copyright'] ) <= 0 ) {
- unset( $footericons['copyright'] );
- }
- }
-
- return $footericons;
- }
-
- /**
- * Output the basic end-page trail including bottomscripts, reporttime, and
- * debug stuff. This should be called right before outputting the closing
- * body and html tags.
- */
- function printTrail() { ?>
-<?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() ); ?>
-<?php $this->html( 'bottomscripts' ); /* JS call to runBodyOnloadHook */ ?>
-<?php $this->html( 'reporttime' ) ?>
-<?php
- }
-}
diff --git a/includes/specialpage/ChangesListSpecialPage.php b/includes/specialpage/ChangesListSpecialPage.php
index c28aa867..b9132358 100644
--- a/includes/specialpage/ChangesListSpecialPage.php
+++ b/includes/specialpage/ChangesListSpecialPage.php
@@ -65,6 +65,12 @@ abstract class ChangesListSpecialPage extends SpecialPage {
$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 );
+ if ( $row->rc_source === RecentChange::SRC_LOG ) {
+ $formatter = LogFormatter::newFromRow( $row );
+ foreach ( $formatter->getPreloadTitles() as $title ) {
+ $batch->addObj( $title );
+ }
+ }
}
$batch->execute();
@@ -154,7 +160,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
protected function getCustomFilters() {
if ( $this->customFilters === null ) {
$this->customFilters = array();
- wfRunHooks( 'ChangesListSpecialPageFilters', array( $this, &$this->customFilters ) );
+ Hooks::run( 'ChangesListSpecialPageFilters', array( $this, &$this->customFilters ) );
}
return $this->customFilters;
@@ -309,17 +315,19 @@ abstract class ChangesListSpecialPage extends SpecialPage {
);
}
- protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
- return wfRunHooks(
+ protected function runMainQueryHook( &$tables, &$fields, &$conds,
+ &$query_options, &$join_conds, $opts
+ ) {
+ return Hooks::run(
'ChangesListSpecialPageQuery',
array( $this->getName(), &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts )
);
}
/**
- * Return a DatabaseBase object for reading
+ * Return a IDatabase object for reading
*
- * @return DatabaseBase
+ * @return IDatabase
*/
protected function getDB() {
return wfGetDB( DB_SLAVE );
diff --git a/includes/specialpage/FormSpecialPage.php b/includes/specialpage/FormSpecialPage.php
index bf86ab2e..90567617 100644
--- a/includes/specialpage/FormSpecialPage.php
+++ b/includes/specialpage/FormSpecialPage.php
@@ -75,21 +75,28 @@ abstract class FormSpecialPage extends SpecialPage {
}
/**
+ * Get display format for the form. See HTMLForm documentation for available values.
+ *
+ * @since 1.25
+ * @return string
+ */
+ protected function getDisplayFormat() {
+ return 'table';
+ }
+
+ /**
* 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 = HTMLForm::factory(
+ $this->getDisplayFormat(),
+ $this->getFormFields(),
+ $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' );
- }
+ $form->setWrapperLegendMsg( $this->getMessagePrefix() . '-legend' );
$headerMsg = $this->msg( $this->getMessagePrefix() . '-text' );
if ( !$headerMsg->isDisabled() ) {
@@ -106,7 +113,7 @@ abstract class FormSpecialPage extends SpecialPage {
$this->alterForm( $form );
// Give hooks a chance to alter the form, adding extra fields or text etc
- wfRunHooks( 'SpecialPageBeforeFormDisplay', array( $this->getName(), &$form ) );
+ Hooks::run( 'SpecialPageBeforeFormDisplay', array( $this->getName(), &$form ) );
return $form;
}
diff --git a/includes/specialpage/ImageQueryPage.php b/includes/specialpage/ImageQueryPage.php
index 272d5337..c4e53eef 100644
--- a/includes/specialpage/ImageQueryPage.php
+++ b/includes/specialpage/ImageQueryPage.php
@@ -35,7 +35,7 @@ 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 IDatabase $dbr (read) connection to use
* @param ResultWrapper $res Result pointer
* @param int $num Number of available result rows
* @param int $offset Paging offset
@@ -59,7 +59,7 @@ abstract class ImageQueryPage extends QueryPage {
}
}
- $out->addHTML( $gallery->toHtml() );
+ $out->addHTML( $gallery->toHTML() );
}
}
diff --git a/includes/specialpage/PageQueryPage.php b/includes/specialpage/PageQueryPage.php
index afc02271..d72744b1 100644
--- a/includes/specialpage/PageQueryPage.php
+++ b/includes/specialpage/PageQueryPage.php
@@ -32,7 +32,7 @@ abstract class PageQueryPage extends QueryPage {
* 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 IDatabase $db
* @param ResultWrapper $res
*/
public function preprocessResults( $db, $res ) {
diff --git a/includes/specialpage/QueryPage.php b/includes/specialpage/QueryPage.php
index b229e06e..1ff7e3fb 100644
--- a/includes/specialpage/QueryPage.php
+++ b/includes/specialpage/QueryPage.php
@@ -60,7 +60,6 @@ abstract class QueryPage extends SpecialPage {
* @return array
*/
public static function getPages() {
- global $wgDisableCounters;
static $qp = null;
if ( $qp === null ) {
@@ -82,7 +81,7 @@ abstract class QueryPage extends SpecialPage {
array( 'MostimagesPage', 'Mostimages' ),
array( 'MostinterwikisPage', 'Mostinterwikis' ),
array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ),
- array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ),
+ array( 'MostlinkedTemplatesPage', 'Mostlinkedtemplates' ),
array( 'MostlinkedPage', 'Mostlinked' ),
array( 'MostrevisionsPage', 'Mostrevisions' ),
array( 'FewestrevisionsPage', 'Fewestrevisions' ),
@@ -97,15 +96,11 @@ abstract class QueryPage extends SpecialPage {
array( 'WantedFilesPage', 'Wantedfiles' ),
array( 'WantedPagesPage', 'Wantedpages' ),
array( 'WantedTemplatesPage', 'Wantedtemplates' ),
- array( 'UnwatchedPagesPage', 'Unwatchedpages' ),
+ array( 'UnwatchedpagesPage', 'Unwatchedpages' ),
array( 'UnusedtemplatesPage', 'Unusedtemplates' ),
array( 'WithoutInterwikiPage', 'Withoutinterwiki' ),
);
- wfRunHooks( 'wgQueryPages', array( &$qp ) );
-
- if ( !$wgDisableCounters ) {
- $qp[] = array( 'PopularPagesPage', 'Popularpages' );
- }
+ Hooks::run( 'wgQueryPages', array( &$qp ) );
}
return $qp;
@@ -351,7 +346,7 @@ abstract class QueryPage extends SpecialPage {
/**
* Get a DB connection to be used for slow recache queries
- * @return DatabaseBase
+ * @return IDatabase
*/
function getRecacheDB() {
return wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) );
@@ -581,7 +576,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 IDatabase $dbr Database (read) connection to use
* @param ResultWrapper $res Result pointer
* @param int $num Number of available result rows
* @param int $offset Paging offset
@@ -654,7 +649,7 @@ abstract class QueryPage extends SpecialPage {
/**
* Do any necessary preprocessing of the result object.
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specialpage/RedirectSpecialPage.php b/includes/specialpage/RedirectSpecialPage.php
index 4226ee02..2e6e55a7 100644
--- a/includes/specialpage/RedirectSpecialPage.php
+++ b/includes/specialpage/RedirectSpecialPage.php
@@ -203,7 +203,7 @@ abstract class RedirectSpecialArticle extends RedirectSpecialPage {
'ctype', 'maxage', 'smaxage',
);
- wfRunHooks( "RedirectSpecialArticleRedirectParams", array( &$redirectParams ) );
+ Hooks::run( "RedirectSpecialArticleRedirectParams", array( &$redirectParams ) );
$this->mAllowedRedirectParams = $redirectParams;
}
}
diff --git a/includes/specialpage/SpecialPage.php b/includes/specialpage/SpecialPage.php
index c0a94af1..a7a43b0e 100644
--- a/includes/specialpage/SpecialPage.php
+++ b/includes/specialpage/SpecialPage.php
@@ -303,26 +303,46 @@ class SpecialPage {
* - `prefixSearchSubpages( "" )` should return `array( foo", "bar", "baz" )`
*
* @param string $search Prefix to search for
- * @param int $limit Maximum number of results to return
+ * @param int $limit Maximum number of results to return (usually 10)
+ * @param int $offset Number of results to skip (usually 0)
* @return string[] Matching subpages
*/
- public function prefixSearchSubpages( $search, $limit = 10 ) {
+ public function prefixSearchSubpages( $search, $limit, $offset ) {
+ $subpages = $this->getSubpagesForPrefixSearch();
+ if ( !$subpages ) {
+ return array();
+ }
+
+ return self::prefixSearchArray( $search, $limit, $subpages, $offset );
+ }
+
+ /**
+ * Return an array of subpages that this special page will accept for prefix
+ * searches. If this method requires a query you might instead want to implement
+ * prefixSearchSubpages() directly so you can support $limit and $offset. This
+ * method is better for static-ish lists of things.
+ *
+ * @return string[] subpages to search from
+ */
+ protected function getSubpagesForPrefixSearch() {
return array();
}
/**
* Helper function for implementations of prefixSearchSubpages() that
- * filter the values in memory (as oppposed to making a query).
+ * filter the values in memory (as opposed to making a query).
*
* @since 1.24
* @param string $search
* @param int $limit
* @param array $subpages
+ * @param int $offset
* @return string[]
*/
- protected static function prefixSearchArray( $search, $limit, array $subpages ) {
+ protected static function prefixSearchArray( $search, $limit, array $subpages, $offset ) {
$escaped = preg_quote( $search, '/' );
- return array_slice( preg_grep( "/^$escaped/i", $subpages ), 0, $limit );
+ return array_slice( preg_grep( "/^$escaped/i",
+ array_slice( $subpages, $offset ) ), 0, $limit );
}
/**
@@ -336,6 +356,7 @@ class SpecialPage {
if ( $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
$out->addModuleStyles( array(
'mediawiki.ui.input',
+ 'mediawiki.ui.radio',
'mediawiki.ui.checkbox',
) );
}
@@ -357,7 +378,7 @@ class SpecialPage {
* @param SpecialPage $this
* @param string|null $subPage
*/
- wfRunHooks( 'SpecialPageBeforeExecute', array( $this, $subPage ) );
+ Hooks::run( 'SpecialPageBeforeExecute', array( $this, $subPage ) );
$this->beforeExecute( $subPage );
$this->execute( $subPage );
@@ -371,7 +392,7 @@ class SpecialPage {
* @param SpecialPage $this
* @param string|null $subPage
*/
- wfRunHooks( 'SpecialPageAfterExecute', array( $this, $subPage ) );
+ Hooks::run( 'SpecialPageAfterExecute', array( $this, $subPage ) );
}
/**
@@ -612,6 +633,26 @@ class SpecialPage {
}
/**
+ * Adds help link with an icon via page indicators.
+ * Link target can be overridden by a local message containing a wikilink:
+ * the message key is: lowercase special page name + '-helppage'.
+ * @param string $to Target MediaWiki.org page title or encoded URL.
+ * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o.
+ * @since 1.25
+ */
+ public function addHelpLink( $to, $overrideBaseUrl = false ) {
+ global $wgContLang;
+ $msg = $this->msg( $wgContLang->lc( $this->getName() ) . '-helppage' );
+
+ if ( !$msg->isDisabled() ) {
+ $helpUrl = Skin::makeUrl( $msg->plain() );
+ $this->getOutput()->addHelpLink( $helpUrl, true );
+ } else {
+ $this->getOutput()->addHelpLink( $to, $overrideBaseUrl );
+ }
+ }
+
+ /**
* 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
diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php
index 23fdc71a..dedfcb6a 100644
--- a/includes/specialpage/SpecialPageFactory.php
+++ b/includes/specialpage/SpecialPageFactory.php
@@ -47,7 +47,7 @@ class SpecialPageFactory {
/**
* List of special page names to the subclass of SpecialPage which handles them.
*/
- private static $list = array(
+ private static $coreList = array(
// Maintenance Reports
'BrokenRedirects' => 'BrokenRedirectsPage',
'Deadendpages' => 'DeadendPagesPage',
@@ -59,7 +59,7 @@ class SpecialPageFactory {
'Withoutinterwiki' => 'WithoutInterwikiPage',
'Protectedpages' => 'SpecialProtectedpages',
'Protectedtitles' => 'SpecialProtectedtitles',
- 'Shortpages' => 'ShortpagesPage',
+ 'Shortpages' => 'ShortPagesPage',
'Uncategorizedcategories' => 'UncategorizedCategoriesPage',
'Uncategorizedimages' => 'UncategorizedImagesPage',
'Uncategorizedpages' => 'UncategorizedPagesPage',
@@ -156,8 +156,10 @@ class SpecialPageFactory {
'Booksources' => 'SpecialBookSources',
// Unlisted / redirects
+ 'ApiHelp' => 'SpecialApiHelp',
'Blankpage' => 'SpecialBlankpage',
'Diff' => 'SpecialDiff',
+ 'EditTags' => 'SpecialEditTags',
'Emailuser' => 'SpecialEmailUser',
'Movepage' => 'MovePageForm',
'Mycontributions' => 'SpecialMycontributions',
@@ -174,6 +176,7 @@ class SpecialPageFactory {
'Userlogout' => 'SpecialUserlogout',
);
+ private static $list;
private static $aliases;
/**
@@ -213,16 +216,13 @@ class SpecialPageFactory {
*/
private static function getPageList() {
global $wgSpecialPages;
- global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
+ global $wgDisableInternalSearch, $wgEmailAuthentication;
global $wgEnableEmail, $wgEnableJavaScriptTest;
global $wgPageLanguageUseDB;
- if ( !is_object( self::$list ) ) {
- wfProfileIn( __METHOD__ );
+ if ( !is_array( self::$list ) ) {
- if ( !$wgDisableCounters ) {
- self::$list['Popularpages'] = 'PopularPagesPage';
- }
+ self::$list = self::$coreList;
if ( !$wgDisableInternalSearch ) {
self::$list['Search'] = 'SpecialSearch';
@@ -250,11 +250,10 @@ class SpecialPageFactory {
// 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 ) );
+ // This hook can be used to disable unwanted core special pages
+ // or conditionally register special pages.
+ Hooks::run( 'SpecialPage_initList', array( &self::$list ) );
- wfProfileOut( __METHOD__ );
}
return self::$list;
@@ -271,12 +270,13 @@ class SpecialPageFactory {
if ( !is_object( self::$aliases ) ) {
global $wgContLang;
$aliases = $wgContLang->getSpecialPageAliases();
+ $pageList = self::getPageList();
self::$aliases = array();
$keepAlias = array();
// Force every canonical name to be an alias for itself.
- foreach ( self::getPageList() as $name => $stuff ) {
+ foreach ( $pageList as $name => $stuff ) {
$caseFoldedAlias = $wgContLang->caseFold( $name );
self::$aliases[$caseFoldedAlias] = $name;
$keepAlias[$caseFoldedAlias] = 'canonical';
@@ -413,7 +413,11 @@ class SpecialPageFactory {
// @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 );
+ $page = ObjectFactory::getObjectFromSpec( array(
+ 'class' => $className,
+ 'args' => $rec,
+ 'closure_expansion' => false,
+ ) );
} elseif ( $rec instanceof SpecialPage ) {
$page = $rec; //XXX: we should deep clone here
} else {
@@ -522,7 +526,6 @@ class SpecialPageFactory {
* @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 );
@@ -544,7 +547,6 @@ class SpecialPageFactory {
}
$context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
- wfProfileOut( __METHOD__ );
return false;
}
@@ -564,14 +566,12 @@ class SpecialPageFactory {
$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;
}
@@ -579,11 +579,7 @@ class SpecialPageFactory {
$page->including( $including );
// Execute special page
- $profName = 'Special:' . $page->getName();
- wfProfileIn( $profName );
$page->run( $par );
- wfProfileOut( $profName );
- wfProfileOut( __METHOD__ );
return true;
}
diff --git a/includes/specialpage/WantedQueryPage.php b/includes/specialpage/WantedQueryPage.php
index be2f1e8d..c4140de1 100644
--- a/includes/specialpage/WantedQueryPage.php
+++ b/includes/specialpage/WantedQueryPage.php
@@ -37,7 +37,7 @@ abstract class WantedQueryPage extends QueryPage {
/**
* Cache page existence for performance
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
@@ -109,7 +109,7 @@ abstract class WantedQueryPage extends QueryPage {
* @note This will only be run if the page is cached (ie $wgMiserMode = true)
* unless forceExistenceCheck() is true.
* @since 1.24
- * @return boolean
+ * @return bool
*/
protected function existenceCheck( Title $title ) {
return $title->isKnown();
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index f983b452..5e2ee1c2 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -130,6 +130,8 @@ class ActiveUsersPager extends UsersPager {
}
function doBatchLookups() {
+ parent::doBatchLookups();
+
$uids = array();
foreach ( $this->mResult as $row ) {
$uids[] = $row->user_id;
@@ -178,7 +180,8 @@ class ActiveUsersPager extends UsersPager {
// Note: This is a different loop than for user rights,
// because we're reusing it to build the group links
// at the same time
- foreach ( $user->getGroups() as $group ) {
+ $groups_list = self::getGroups( intval( $row->user_id ), $this->userGroupCache );
+ foreach ( $groups_list as $group ) {
if ( in_array( $group, $this->hideGroups ) ) {
return '';
}
@@ -211,7 +214,8 @@ class ActiveUsersPager extends UsersPager {
# Username field
$out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(),
- 'username', 'offset', 20, $this->requestedUser, array( 'tabindex' => 1 ) ) . '<br />';
+ 'username', 'offset', 20, $this->requestedUser,
+ array( 'class' => 'mw-ui-input-inline', 'tabindex' => 1 ) ) . '<br />';
$out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(),
'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ), array( 'tabindex' => 2 ) );
@@ -263,11 +267,22 @@ class SpecialActiveUsers extends SpecialPage {
$out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>",
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 ) );
+ // Get the timestamp of the last cache update
+ $dbr = wfGetDB( DB_SLAVE, 'recentchanges' );
+ $cTime = $dbr->selectField( 'querycache_info',
+ 'qci_timestamp',
+ array( 'qci_type' => 'activeusers' )
+ );
+
+ $secondsOld = $cTime
+ ? time() - wfTimestamp( TS_UNIX, $cTime )
+ : $days * 86400; // fully stale :)
+
+ if ( $secondsOld > 0 ) {
+ // Mention the level of staleness
+ $out->addWikiMsg( 'cachedspecial-viewing-cached-ttl',
+ $this->getLanguage()->formatDuration( $secondsOld ) );
+ }
$up = new ActiveUsersPager( $this->getContext(), null, $par );
@@ -289,149 +304,4 @@ 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 96be4d03..7b596bb0 100644
--- a/includes/specials/SpecialAllMessages.php
+++ b/includes/specials/SpecialAllMessages.php
@@ -59,6 +59,7 @@ class SpecialAllMessages extends SpecialPage {
$this->outputHeader( 'allmessagestext' );
$out->addModuleStyles( 'mediawiki.special' );
+ $this->addHelpLink( 'Help:System message' );
$this->table = new AllmessagesTablePager(
$this,
@@ -223,19 +224,17 @@ class AllMessagesTablePager extends TablePager {
}
function getAllMessages( $descending ) {
- wfProfileIn( __METHOD__ );
$messageNames = Language::getLocalisationCache()->getSubitemList( 'en', 'messages' );
+
+ // Normalise message names so they look like page titles and sort correctly - T86139
+ $messageNames = array_map( array( $this->lang, 'ucfirst' ), $messageNames );
+
if ( $descending ) {
rsort( $messageNames );
} else {
asort( $messageNames );
}
- // Normalise message names so they look like page titles
- $messageNames = array_map( array( $this->lang, 'ucfirst' ), $messageNames );
-
- wfProfileOut( __METHOD__ );
-
return $messageNames;
}
@@ -252,7 +251,6 @@ class AllMessagesTablePager extends TablePager {
*/
public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) {
// FIXME: This function should be moved to Language:: or something.
- wfProfileIn( __METHOD__ . '-db' );
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'page',
@@ -288,8 +286,6 @@ class AllMessagesTablePager extends TablePager {
}
}
- wfProfileOut( __METHOD__ . '-db' );
-
return array( 'pages' => $pageFlags, 'talks' => $talkFlags );
}
diff --git a/includes/specials/SpecialAllPages.php b/includes/specials/SpecialAllPages.php
index 08b8761a..74b1f7bb 100644
--- a/includes/specials/SpecialAllPages.php
+++ b/includes/specials/SpecialAllPages.php
@@ -101,7 +101,10 @@ class SpecialAllPages extends IncludableSpecialPage {
$t = $this->getPageTitle();
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
- $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getConfig()->get( 'Script' ) ) );
+ $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() );
diff --git a/includes/specials/SpecialApiHelp.php b/includes/specials/SpecialApiHelp.php
new file mode 100644
index 00000000..b43911f5
--- /dev/null
+++ b/includes/specials/SpecialApiHelp.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Implements Special:ApiHelp
+ *
+ * This 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 to redirect to API help pages, for situations where linking to
+ * the api.php endpoint is not wanted.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialApiHelp extends UnlistedSpecialPage {
+ public function __construct() {
+ parent::__construct( 'ApiHelp' );
+ }
+
+ public function execute( $par ) {
+ if ( empty( $par ) ) {
+ $par = 'main';
+ }
+
+ // These come from transclusions
+ $request = $this->getRequest();
+ $options = array(
+ 'action' => 'help',
+ 'nolead' => true,
+ 'submodules' => $request->getCheck( 'submodules' ),
+ 'recursivesubmodules' => $request->getCheck( 'recursivesubmodules' ),
+ 'title' => $request->getVal( 'title', $this->getPageTitle( '$1' )->getPrefixedText() ),
+ );
+
+ // These are for linking from wikitext, since url parameters are a pain
+ // to do.
+ while ( true ) {
+ if ( substr( $par, 0, 4 ) === 'sub/' ) {
+ $par = substr( $par, 4 );
+ $options['submodules'] = 1;
+ continue;
+ }
+
+ if ( substr( $par, 0, 5 ) === 'rsub/' ) {
+ $par = substr( $par, 5 );
+ $options['recursivesubmodules'] = 1;
+ continue;
+ }
+
+ $moduleName = $par;
+ break;
+ }
+
+ if ( !$this->including() ) {
+ unset( $options['nolead'], $options['title'] );
+ $options['modules'] = $moduleName;
+ $link = wfAppendQuery( wfExpandUrl( wfScript( 'api' ), PROTO_CURRENT ), $options );
+ $this->getOutput()->redirect( $link );
+ return;
+ }
+
+ $main = new ApiMain( $this->getContext(), false );
+ try {
+ $module = $main->getModuleFromPath( $moduleName );
+ } catch ( UsageException $ex ) {
+ $this->getOutput()->addHTML( Html::rawElement( 'span', array( 'class' => 'error' ),
+ $this->msg( 'apihelp-no-such-module', $moduleName )->inContentLanguage()->parse()
+ ) );
+ return;
+ }
+
+ ApiHelp::getHelp( $this->getContext(), $module, $options );
+ }
+
+ public function isIncludable() {
+ return true;
+ }
+}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index 3297c17a..b80d921d 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -98,13 +98,16 @@ class SpecialBlock extends FormSpecialPage {
$form->setWrapperLegendMsg( 'blockip-legend' );
$form->setHeaderText( '' );
$form->setSubmitCallback( array( __CLASS__, 'processUIForm' ) );
+ $form->setSubmitDestructive();
$msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
$form->setSubmitTextMsg( $msg );
+ $this->addHelpLink( 'Help:Blocking users' );
+
# Don't need to do anything if the form has been posted
if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
- $s = HTMLForm::formatErrors( $this->preErrors );
+ $s = $form->formatErrors( $this->preErrors );
if ( $s ) {
$form->addHeaderText( Html::rawElement(
'div',
@@ -135,6 +138,7 @@ class SpecialBlock extends FormSpecialPage {
'autofocus' => true,
'required' => true,
'validation-callback' => array( __CLASS__, 'validateTargetField' ),
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
),
'Expiry' => array(
'type' => !count( $suggestedDurations ) ? 'text' : 'selectorother',
@@ -146,6 +150,7 @@ class SpecialBlock extends FormSpecialPage {
),
'Reason' => array(
'type' => 'selectandother',
+ 'maxlength' => 255,
'label-message' => 'ipbreason',
'options-message' => 'ipbreason-dropdown',
),
@@ -217,7 +222,7 @@ class SpecialBlock extends FormSpecialPage {
$this->maybeAlterFormDefaults( $a );
// Allow extensions to add more fields
- wfRunHooks( 'SpecialBlockModifyFormFields', array( $this, &$a ) );
+ Hooks::run( 'SpecialBlockModifyFormFields', array( $this, &$a ) );
return $a;
}
@@ -233,6 +238,14 @@ class SpecialBlock extends FormSpecialPage {
# This will be overwritten by request data
$fields['Target']['default'] = (string)$this->target;
+ if ( $this->target ) {
+ $status = self::validateTarget( $this->target, $this->getUser() );
+ if ( !$status->isOK() ) {
+ $errors = $status->getErrorsArray();
+ $this->preErrors = array_merge( $this->preErrors, $errors );
+ }
+ }
+
# This won't be
$fields['PreviousTarget']['default'] = (string)$this->target;
@@ -307,14 +320,14 @@ class SpecialBlock extends FormSpecialPage {
* @return string
*/
protected function preText() {
- $this->getOutput()->addModules( 'mediawiki.special.block' );
+ $this->getOutput()->addModules( array( 'mediawiki.special.block', 'mediawiki.userSuggest' ) );
$text = $this->msg( 'blockiptext' )->parse();
$otherBlockMessages = array();
if ( $this->target !== null ) {
# Get other blocks, i.e. from GlobalBlocking or TorBlock extension
- wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockMessages, $this->target ) );
+ Hooks::run( 'OtherBlockLogLink', array( &$otherBlockMessages, $this->target ) );
if ( count( $otherBlockMessages ) ) {
$s = Html::rawElement(
@@ -623,7 +636,7 @@ class SpecialBlock extends FormSpecialPage {
# permission anyway, although the code does allow for it.
# Note: Important to use $target instead of $data['Target']
# since both $data['PreviousTarget'] and $target are normalized
- # but $data['target'] gets overriden by (non-normalized) request variable
+ # but $data['target'] gets overridden by (non-normalized) request variable
# from previous request.
if ( $target === $performer->getName() &&
( $data['PreviousTarget'] !== $target || !$data['Confirm'] )
@@ -668,7 +681,7 @@ class SpecialBlock extends FormSpecialPage {
# Recheck params here...
if ( $type != Block::TYPE_USER ) {
$data['HideUser'] = false; # IP users should not be hidden
- } elseif ( !in_array( $data['Expiry'], array( 'infinite', 'infinity', 'indefinite' ) ) ) {
+ } elseif ( !wfIsInfinity( $data['Expiry'] ) ) {
# Bad expiry.
return array( 'ipb_expiry_temp' );
} elseif ( $wgHideUserContribLimit !== false
@@ -698,7 +711,7 @@ class SpecialBlock extends FormSpecialPage {
$block->mHideName = $data['HideUser'];
$reason = array( 'hookaborted' );
- if ( !wfRunHooks( 'BlockIp', array( &$block, &$performer, &$reason ) ) ) {
+ if ( !Hooks::run( 'BlockIp', array( &$block, &$performer, &$reason ) ) ) {
return $reason;
}
@@ -759,7 +772,7 @@ class SpecialBlock extends FormSpecialPage {
$logaction = 'block';
}
- wfRunHooks( 'BlockIpComplete', array( $block, $performer ) );
+ Hooks::run( 'BlockIpComplete', array( $block, $performer ) );
# Set *_deleted fields if requested
if ( $data['HideUser'] ) {
@@ -781,22 +794,21 @@ class SpecialBlock extends FormSpecialPage {
# Prepare log parameters
$logParams = array();
- $logParams[] = $data['Expiry'];
- $logParams[] = self::blockLogFlags( $data, $type );
+ $logParams['5::duration'] = $data['Expiry'];
+ $logParams['6::flags'] = self::blockLogFlags( $data, $type );
# Make log entry, if the name is hidden, put it in the oversight log
$log_type = $data['HideUser'] ? 'suppress' : 'block';
- $log = new LogPage( $log_type );
- $log_id = $log->addEntry(
- $logaction,
- Title::makeTitle( NS_USER, $target ),
- $data['Reason'][0],
- $logParams,
- $performer
- );
+ $logEntry = new ManualLogEntry( $log_type, $logaction );
+ $logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
+ $logEntry->setComment( $data['Reason'][0] );
+ $logEntry->setPerformer( $performer );
+ $logEntry->setParameters( $logParams );
# Relate log ID to block IDs (bug 25763)
$blockIds = array_merge( array( $status['id'] ), $status['autoIds'] );
- $log->addRelations( 'ipb_id', $blockIds, $log_id );
+ $logEntry->setRelations( array( 'ipb_id' => $blockIds ) );
+ $logId = $logEntry->insert();
+ $logEntry->publish( $logId );
# Report to the user
return true;
@@ -826,7 +838,7 @@ class SpecialBlock extends FormSpecialPage {
}
list( $show, $value ) = explode( ':', $option );
- $a[htmlspecialchars( $show )] = htmlspecialchars( $value );
+ $a[$show] = $value;
}
return $a;
@@ -844,7 +856,7 @@ class SpecialBlock extends FormSpecialPage {
$infinity = wfGetDB( DB_SLAVE )->getInfinity();
}
- if ( $expiry == 'infinite' || $expiry == 'indefinite' ) {
+ if ( wfIsInfinity( $expiry ) ) {
$expiry = $infinity;
} else {
$expiry = strtotime( $expiry );
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index 456f4ecb..0ec144a2 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -47,6 +47,7 @@ class SpecialBlockList extends SpecialPage {
$lang = $this->getLanguage();
$out->setPageTitle( $this->msg( 'ipblocklist' ) );
$out->addModuleStyles( 'mediawiki.special' );
+ $out->addModules( 'mediawiki.userSuggest' );
$request = $this->getRequest();
$par = $request->getVal( 'ip', $par );
@@ -72,6 +73,7 @@ class SpecialBlockList extends SpecialPage {
'tabindex' => '1',
'size' => '45',
'default' => $this->target,
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
),
'Options' => array(
'type' => 'multiselect',
@@ -103,6 +105,7 @@ class SpecialBlockList extends SpecialPage {
$form->setMethod( 'get' );
$form->setWrapperLegendMsg( 'ipblocklist-legend' );
$form->setSubmitTextMsg( 'ipblocklist-submit' );
+ $form->setSubmitProgressive();
$form->prepareForm();
$form->displayForm( '' );
@@ -110,11 +113,6 @@ class SpecialBlockList extends SpecialPage {
}
function showList() {
- # Purge expired entries on one in every 10 queries
- if ( !mt_rand( 0, 10 ) ) {
- Block::purgeExpired();
- }
-
$conds = array();
# Is the user allowed to see hidden blocks?
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
@@ -167,7 +165,7 @@ class SpecialBlockList extends SpecialPage {
# Check for other blocks, i.e. global/tor blocks
$otherBlockLink = array();
- wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockLink, $this->target ) );
+ Hooks::run( 'OtherBlockLogLink', array( &$otherBlockLink, $this->target ) );
$out = $this->getOutput();
@@ -259,7 +257,6 @@ class BlockListPager extends TablePager {
'blocklist-nousertalk',
'unblocklink',
'change-blocklink',
- 'infiniteblock',
);
$msg = array_combine( $msg, array_map( array( $this, 'msg' ), $msg ) );
}
@@ -396,6 +393,10 @@ class BlockListPager extends TablePager {
'join_conds' => array( 'user' => array( 'LEFT JOIN', 'user_id = ipb_by' ) )
);
+ # Filter out any expired blocks
+ $db = $this->getDatabase();
+ $info['conds'][] = 'ipb_expiry > ' . $db->addQuotes( $db->timestamp() );
+
# Is the user allowed to see hidden blocks?
if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$info['conds']['ipb_deleted'] = 0;
@@ -425,7 +426,6 @@ class BlockListPager extends TablePager {
* @param ResultWrapper $result
*/
function preprocessResults( $result ) {
- wfProfileIn( __METHOD__ );
# Do a link batch query
$lb = new LinkBatch;
$lb->setCaller( __METHOD__ );
@@ -450,6 +450,5 @@ class BlockListPager extends TablePager {
}
$lb->execute();
- wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index 72f4e466..7028fdcb 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -26,7 +26,6 @@
* The parser creates links to this page when dealing with ISBNs in wikitext
*
* @author Rob Church <robchur@gmail.com>
- * @todo Validate ISBNs using the standard check-digit method
* @ingroup SpecialPage
*/
class SpecialBookSources extends SpecialPage {
@@ -73,7 +72,9 @@ class SpecialBookSources extends SpecialPage {
$sum = 0;
if ( strlen( $isbn ) == 13 ) {
for ( $i = 0; $i < 12; $i++ ) {
- if ( $i % 2 == 0 ) {
+ if ( $isbn[$i] === 'X' ) {
+ return false;
+ } elseif ( $i % 2 == 0 ) {
$sum += $isbn[$i];
} else {
$sum += 3 * $isbn[$i];
@@ -81,11 +82,14 @@ class SpecialBookSources extends SpecialPage {
}
$check = ( 10 - ( $sum % 10 ) ) % 10;
- if ( $check == $isbn[12] ) {
+ if ( (string)$check === $isbn[12] ) {
return true;
}
} elseif ( strlen( $isbn ) == 10 ) {
for ( $i = 0; $i < 9; $i++ ) {
+ if ( $isbn[$i] === 'X' ) {
+ return false;
+ }
$sum += $isbn[$i] * ( $i + 1 );
}
@@ -93,7 +97,7 @@ class SpecialBookSources extends SpecialPage {
if ( $check == 10 ) {
$check = "X";
}
- if ( $check == $isbn[9] ) {
+ if ( (string)$check === $isbn[9] ) {
return true;
}
}
@@ -131,9 +135,14 @@ class SpecialBookSources extends SpecialPage {
'isbn',
20,
$this->isbn,
- array( 'autofocus' => true )
+ array( 'autofocus' => '', 'class' => 'mw-ui-input-inline' )
);
- $form .= '&#160;' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . "</p>\n";
+
+ $form .= '&#160;' . Html::submitButton(
+ $this->msg( 'booksources-search' )->text(),
+ array(), array( 'mw-ui-progressive' )
+ ) . "</p>\n";
+
$form .= Html::closeElement( 'form' ) . "\n";
$form .= Html::closeElement( 'fieldset' ) . "\n";
@@ -152,7 +161,7 @@ class SpecialBookSources extends SpecialPage {
# Hook to allow extensions to insert additional HTML,
# e.g. for API-interacting plugins and so on
- wfRunHooks( 'BookInformation', array( $this->isbn, $this->getOutput() ) );
+ Hooks::run( 'BookInformation', array( $this->isbn, $this->getOutput() ) );
# Check for a local page such as Project:Book_sources and use that if available
$page = $this->msg( 'booksources' )->inContentLanguage()->text();
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index 95f9efd2..3a13b7ed 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -188,9 +188,11 @@ class CategoryPager extends AlphabeticPager {
$this->msg( 'categories' )->text(),
Xml::inputLabel(
$this->msg( 'categoriesfrom' )->text(),
- 'from', 'from', 20, $from ) .
+ 'from', 'from', 20, $from, array( 'class' => 'mw-ui-input-inline' ) ) .
' ' .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text()
+ Html::submitButton(
+ $this->msg( 'allpagessubmit' )->text(),
+ array(), array( 'mw-ui-progressive' )
)
)
);
diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php
index e0be838b..eca307d9 100644
--- a/includes/specials/SpecialChangeEmail.php
+++ b/includes/specials/SpecialChangeEmail.php
@@ -39,7 +39,7 @@ class SpecialChangeEmail extends FormSpecialPage {
/**
* @return bool
*/
- function isListed() {
+ public function isListed() {
global $wgAuth;
return $wgAuth->allowPropChange( 'emailaddress' );
@@ -54,7 +54,7 @@ class SpecialChangeEmail extends FormSpecialPage {
$out->disallowUserJs();
$out->addModules( 'mediawiki.special.changeemail' );
- return parent::execute( $par );
+ parent::execute( $par );
}
protected function checkExecutePermissions( User $user ) {
@@ -106,22 +106,20 @@ class SpecialChangeEmail extends FormSpecialPage {
return $fields;
}
+ protected function getDisplayFormat() {
+ return 'vform';
+ }
+
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' ) );
+ $form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
}
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'] );
- }
+ $password = isset( $data['Password'] ) ? $data['Password'] : null;
+ $status = $this->attemptChange( $this->getUser(), $password, $data['NewEmail'] );
$this->status = $status;
@@ -129,18 +127,22 @@ class SpecialChangeEmail extends FormSpecialPage {
}
public function onSuccess() {
- $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
+ $request = $this->getRequest();
+
+ $titleObj = Title::newFromText( $request->getVal( 'returnto' ) );
if ( !$titleObj instanceof Title ) {
$titleObj = Title::newMainPage();
}
+ $query = $request->getVal( 'returntoquery' );
if ( $this->status->value === true ) {
- $this->getOutput()->redirect( $titleObj->getFullURL() );
+ $this->getOutput()->redirect( $titleObj->getFullURL( $query ) );
} 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
+ // just show the link to go back
+ $this->getOutput()->addReturnTo( $titleObj, wfCgiToArray( $query ) );
}
}
@@ -150,7 +152,7 @@ class SpecialChangeEmail extends FormSpecialPage {
* @param string $newaddr
* @return Status
*/
- protected function attemptChange( User $user, $pass, $newaddr ) {
+ private function attemptChange( User $user, $pass, $newaddr ) {
global $wgAuth;
if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
@@ -184,7 +186,7 @@ class SpecialChangeEmail extends FormSpecialPage {
return $status;
}
- wfRunHooks( 'PrefsEmailAudit', array( $user, $oldaddr, $newaddr ) );
+ Hooks::run( 'PrefsEmailAudit', array( $user, $oldaddr, $newaddr ) );
$user->saveSettings();
diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php
index 24664edb..168095f8 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -118,7 +118,7 @@ class SpecialChangePassword extends FormSpecialPage {
}
$extraFields = array();
- wfRunHooks( 'ChangePasswordForm', array( &$extraFields ) );
+ Hooks::run( 'ChangePasswordForm', array( &$extraFields ) );
foreach ( $extraFields as $extra ) {
list( $name, $label, $type, $default ) = $extra;
$fields[$name] = array(
@@ -248,7 +248,7 @@ class SpecialChangePassword extends FormSpecialPage {
}
if ( $newpass !== $retype ) {
- wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) );
+ Hooks::run( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) );
throw new PasswordError( $this->msg( 'badretype' )->text() );
}
@@ -264,7 +264,7 @@ class SpecialChangePassword extends FormSpecialPage {
// @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' ) );
+ Hooks::run( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() );
}
@@ -276,8 +276,8 @@ class SpecialChangePassword extends FormSpecialPage {
// 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, $oldpass, $newpass, &$abortMsg ) ) ) {
- wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'abortreset' ) );
+ if ( !Hooks::run( 'AbortChangePassword', array( $user, $oldpass, $newpass, &$abortMsg ) ) ) {
+ Hooks::run( 'PrefsPasswordAudit', array( $user, $newpass, 'abortreset' ) );
throw new PasswordError( $this->msg( $abortMsg )->text() );
}
@@ -288,9 +288,9 @@ class SpecialChangePassword extends FormSpecialPage {
try {
$user->setPassword( $newpass );
- wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) );
+ Hooks::run( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) );
} catch ( PasswordError $e ) {
- wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
+ Hooks::run( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
throw new PasswordError( $e->getMessage() );
}
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index d771589d..b6ab112b 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -38,6 +38,9 @@ class EmailConfirmation extends UnlistedSpecialPage {
* Main execution point
*
* @param null|string $code Confirmation code passed to the page
+ * @throws PermissionsError
+ * @throws ReadOnlyError
+ * @throws UserNotLoggedIn
*/
function execute( $code ) {
$this->setHeaders();
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index 32a887c4..c2cd8122 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -176,7 +176,7 @@ class SpecialContributions extends IncludableSpecialPage {
// Add RSS/atom links
$this->addFeedLinks( $feedParams );
- if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id, $userObj, $this ) ) ) {
+ if ( Hooks::run( 'SpecialContributionsBeforeMainOutput', array( $id, $userObj, $this ) ) ) {
if ( !$this->including() ) {
$out->addHTML( $this->getForm() );
}
@@ -386,7 +386,7 @@ class SpecialContributions extends IncludableSpecialPage {
);
}
- wfRunHooks( 'ContributionsToolLinks', array( $id, $userpage, &$tools ) );
+ Hooks::run( 'ContributionsToolLinks', array( $id, $userpage, &$tools ) );
return $tools;
}
@@ -478,18 +478,15 @@ class SpecialContributions extends IncludableSpecialPage {
if ( $tagFilter ) {
$filterSelection = Html::rawElement(
'td',
- array( 'class' => 'mw-label' ),
- array_shift( $tagFilter )
- );
- $filterSelection .= Html::rawElement(
- 'td',
- array( 'class' => 'mw-input' ),
- implode( '&#160', $tagFilter )
+ array(),
+ array_shift( $tagFilter ) . implode( '&#160', $tagFilter )
);
} else {
$filterSelection = Html::rawElement( 'td', array( 'colspan' => 2 ), '' );
}
+ $this->getOutput()->addModules( 'mediawiki.userSuggest' );
+
$labelNewbies = Xml::radioLabel(
$this->msg( 'sp-contributions-newbies' )->text(),
'contribs',
@@ -510,9 +507,15 @@ class SpecialContributions extends IncludableSpecialPage {
'target',
$this->opts['target'],
'text',
- array( 'size' => '40', 'required' => '', 'class' => 'mw-input' ) +
- ( $this->opts['target'] ? array() : array( 'autofocus' )
- )
+ array(
+ 'size' => '40',
+ 'required' => '',
+ 'class' => array(
+ 'mw-input',
+ 'mw-ui-input-inline',
+ 'mw-autocomplete-user', // used by mediawiki.userSuggest
+ ),
+ ) + ( $this->opts['target'] ? array() : array( 'autofocus' ) )
);
$targetSelection = Html::rawElement(
'td',
@@ -522,16 +525,12 @@ class SpecialContributions extends IncludableSpecialPage {
$namespaceSelection = Xml::tags(
'td',
- array( 'class' => 'mw-label' ),
+ array(),
Xml::label(
$this->msg( 'namespace' )->text(),
'namespace',
''
- )
- );
- $namespaceSelection .= Html::rawElement(
- 'td',
- null,
+ ) .
Html::namespaceSelector(
array( 'selected' => $this->opts['namespace'], 'all' => '' ),
array(
@@ -617,9 +616,9 @@ class SpecialContributions extends IncludableSpecialPage {
$this->opts['year'] === '' ? MWTimestamp::getInstance()->format( 'Y' ) : $this->opts['year'],
$this->opts['month']
) . ' ' .
- Xml::submitButton(
+ Html::submitButton(
$this->msg( 'sp-contributions-submit' )->text(),
- array( 'class' => 'mw-submit' )
+ array( 'class' => 'mw-submit' ), array( 'mw-ui-progressive' )
)
);
@@ -659,7 +658,7 @@ class ContribsPager extends ReverseChronologicalPager {
public $mDb;
public $preventClickjacking = false;
- /** @var DatabaseBase */
+ /** @var IDatabase */
public $mDbSecondary;
/**
@@ -673,10 +672,7 @@ class ContribsPager extends ReverseChronologicalPager {
$msgs = array(
'diff',
'hist',
- 'newarticle',
'pipe-separator',
- 'rev-delundel',
- 'rollbacklink',
'uctop'
);
@@ -715,7 +711,7 @@ 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.
+ * a hook added, to allow extensions to add additional queries.
*
* @param string $offset Index offset, inclusive
* @param int $limit Exact query limit
@@ -751,7 +747,7 @@ class ContribsPager extends ReverseChronologicalPager {
$data = array( $this->mDb->select(
$tables, $fields, $conds, $fname, $options, $join_conds
) );
- wfRunHooks(
+ Hooks::run(
'ContribsPager::reallyDoQuery',
array( &$data, $pager, $offset, $limit, $descending )
);
@@ -828,7 +824,7 @@ class ContribsPager extends ReverseChronologicalPager {
$this->tagFilter
);
- wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) );
+ Hooks::run( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) );
return $queryInfo;
}
@@ -935,7 +931,7 @@ class ContribsPager extends ReverseChronologicalPager {
* @return string
*/
function getStartBody() {
- return "<ul>\n";
+ return "<ul class=\"mw-contributions-list\">\n";
}
/**
@@ -958,7 +954,6 @@ class ContribsPager extends ReverseChronologicalPager {
* @return string
*/
function formatRow( $row ) {
- wfProfileIn( __METHOD__ );
$ret = '';
$classes = array();
@@ -974,7 +969,7 @@ class ContribsPager extends ReverseChronologicalPager {
try {
$rev = new Revision( $row );
$validRevision = (bool)$rev->getId();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$validRevision = false;
}
wfRestoreWarnings();
@@ -1113,7 +1108,7 @@ class ContribsPager extends ReverseChronologicalPager {
}
// Let extensions add data
- wfRunHooks( 'ContributionsLineEnding', array( $this, &$ret, $row, &$classes ) );
+ Hooks::run( 'ContributionsLineEnding', array( $this, &$ret, $row, &$classes ) );
if ( $classes === array() && $ret === '' ) {
wfDebug( "Dropping Special:Contribution row that could not be formatted\n" );
@@ -1122,8 +1117,6 @@ class ContribsPager extends ReverseChronologicalPager {
$ret = Html::rawElement( 'li', array( 'class' => $classes ), $ret ) . "\n";
}
- wfProfileOut( __METHOD__ );
-
return $ret;
}
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index 68f2c469..9e4bbbe5 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -78,6 +78,53 @@ class DeletedContribsPager extends IndexPager {
);
}
+ /**
+ * This method basically executes the exact same code as the parent class, though with
+ * a hook added, to allow extensions to add additional queries.
+ *
+ * @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 ) {
+ $pager = $this;
+
+ $data = array( parent::reallyDoQuery( $offset, $limit, $descending ) );
+
+ // This hook will allow extensions to add in additional queries, nearly
+ // identical to ContribsPager::reallyDoQuery.
+ Hooks::run(
+ 'DeletedContribsPager::reallyDoQuery',
+ array( &$data, $pager, $offset, $limit, $descending )
+ );
+
+ $result = array();
+
+ // loop all results and collect them in an array
+ foreach ( $data as $query ) {
+ foreach ( $query as $i => $row ) {
+ // use index column as key, allowing us to easily sort in PHP
+ $result[$row->{$this->getIndexField()} . "-$i"] = $row;
+ }
+ }
+
+ // sort results
+ if ( $descending ) {
+ ksort( $result );
+ } else {
+ krsort( $result );
+ }
+
+ // enforce limit
+ $result = array_slice( $result, 0, $limit );
+
+ // get rid of array keys
+ $result = array_values( $result );
+
+ return new FakeResultWrapper( $result );
+ }
+
function getUserCond() {
$condition = array();
@@ -141,6 +188,50 @@ class DeletedContribsPager extends IndexPager {
/**
* Generates each row in the contributions list.
*
+ * @todo This would probably look a lot nicer in a table.
+ * @param stdClass $row
+ * @return string
+ */
+ function formatRow( $row ) {
+ $ret = '';
+ $classes = array();
+
+ /*
+ * There may be more than just revision rows. To make sure that we'll only be processing
+ * revisions here, let's _try_ to build a revision out of our row (without displaying
+ * notices though) and then trying to grab data from the built object. If we succeed,
+ * we're definitely dealing with revision data and we may proceed, if not, we'll leave it
+ * to extensions to subscribe to the hook to parse the row.
+ */
+ wfSuppressWarnings();
+ try {
+ $rev = Revision::newFromArchiveRow( $row );
+ $validRevision = (bool)$rev->getId();
+ } catch ( Exception $e ) {
+ $validRevision = false;
+ }
+ wfRestoreWarnings();
+
+ if ( $validRevision ) {
+ $ret = $this->formatRevisionRow( $row );
+ }
+
+ // Let extensions add data
+ Hooks::run( 'DeletedContributionsLineEnding', array( $this, &$ret, $row, &$classes ) );
+
+ if ( $classes === array() && $ret === '' ) {
+ wfDebug( "Dropping Special:DeletedContribution row that could not be formatted\n" );
+ $ret = "<!-- Could not format Special:DeletedContribution row. -->\n";
+ } else {
+ $ret = Html::rawElement( 'li', array( 'class' => $classes ), $ret ) . "\n";
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Generates each row in the contributions list for archive entries.
+ *
* Contributions which are marked "top" are currently on top of the history.
* For these contributions, a [rollback] link is shown for users with sysop
* privileges. The rollback link restores the most recent version that was not
@@ -150,9 +241,7 @@ class DeletedContribsPager extends IndexPager {
* @param stdClass $row
* @return string
*/
- function formatRow( $row ) {
- wfProfileIn( __METHOD__ );
-
+ function formatRevisionRow( $row ) {
$page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
$rev = new Revision( array(
@@ -256,17 +345,13 @@ class DeletedContribsPager extends IndexPager {
$ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
}
- $ret = Html::rawElement( 'li', array(), $ret ) . "\n";
-
- wfProfileOut( __METHOD__ );
-
return $ret;
}
/**
* Get the Database object in use
*
- * @return DatabaseBase
+ * @return IDatabase
*/
public function getDatabase() {
return $this->mDb;
@@ -315,7 +400,8 @@ class DeletedContributionsPage extends SpecialPage {
return;
}
- $options['limit'] = $request->getInt( 'limit', $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
+ $options['limit'] = $request->getInt( 'limit',
+ $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
$options['target'] = $target;
$userObj = User::newFromName( $target, false );
@@ -465,7 +551,7 @@ class DeletedContributionsPage extends SpecialPage {
);
}
- wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) );
+ Hooks::run( 'ContributionsToolLinks', array( $id, $nt, &$tools ) );
$links = $this->getLanguage()->pipeList( $tools );
@@ -533,6 +619,8 @@ class DeletedContributionsPage extends SpecialPage {
$f .= "\t" . Html::hidden( $name, $value ) . "\n";
}
+ $this->getOutput()->addModules( 'mediawiki.userSuggest' );
+
$f .= Xml::openElement( 'fieldset' );
$f .= Xml::element( 'legend', array(), $this->msg( 'sp-contributions-search' )->text() );
$f .= Xml::tags(
@@ -546,7 +634,10 @@ class DeletedContributionsPage extends SpecialPage {
'text',
array(
'size' => '20',
- 'required' => ''
+ 'required' => '',
+ 'class' => array(
+ 'mw-autocomplete-user', // used by mediawiki.userSuggest
+ ),
) + ( $options['target'] ? array() : array( 'autofocus' ) )
) . ' ';
$f .= Html::namespaceSelector(
diff --git a/includes/specials/SpecialDiff.php b/includes/specials/SpecialDiff.php
index 77d23173..89c1c021 100644
--- a/includes/specials/SpecialDiff.php
+++ b/includes/specials/SpecialDiff.php
@@ -53,6 +53,7 @@ class SpecialDiff extends RedirectSpecialPage {
$this->mAddedRedirectParams['diff'] = $parts[1];
} else {
// Wrong number of parameters, bail out
+ $this->addHelpLink( 'Help:Diff' );
throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
}
diff --git a/includes/specials/SpecialEditTags.php b/includes/specials/SpecialEditTags.php
new file mode 100644
index 00000000..f41a1f1d
--- /dev/null
+++ b/includes/specials/SpecialEditTags.php
@@ -0,0 +1,460 @@
+<?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 SpecialPage
+ */
+
+/**
+ * Special page for adding and removing change tags to individual revisions.
+ * A lot of this is copied out of SpecialRevisiondelete.
+ *
+ * @ingroup SpecialPage
+ * @since 1.25
+ */
+class SpecialEditTags extends UnlistedSpecialPage {
+ /** @var bool Was the DB modified in this request */
+ protected $wasSaved = false;
+
+ /** @var bool True if the submit button was clicked, and the form was posted */
+ private $submitClicked;
+
+ /** @var array Target ID list */
+ private $ids;
+
+ /** @var Title Title object for target parameter */
+ private $targetObj;
+
+ /** @var string Deletion type, may be revision or logentry */
+ private $typeName;
+
+ /** @var ChangeTagsList Storing the list of items to be tagged */
+ private $revList;
+
+ /** @var bool Whether user is allowed to perform the action */
+ private $isAllowed;
+
+ /** @var string */
+ private $reason;
+
+ public function __construct() {
+ parent::__construct( 'EditTags', 'changetags' );
+ }
+
+ public function execute( $par ) {
+ $this->checkPermissions();
+ $this->checkReadOnly();
+
+ $output = $this->getOutput();
+ $user = $this->getUser();
+ $request = $this->getRequest();
+
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $this->getOutput()->addModules( array( 'mediawiki.special.edittags',
+ 'mediawiki.special.edittags.styles' ) );
+
+ $this->submitClicked = $request->wasPosted() && $request->getBool( 'wpSubmit' );
+
+ // Handle our many different possible input types
+ $ids = $request->getVal( 'ids' );
+ if ( !is_null( $ids ) ) {
+ // Allow CSV from the form hidden field, or a single ID for show/hide links
+ $this->ids = explode( ',', $ids );
+ } else {
+ // Array input
+ $this->ids = array_keys( $request->getArray( 'ids', array() ) );
+ }
+ $this->ids = array_unique( array_filter( $this->ids ) );
+
+ // No targets?
+ if ( count( $this->ids ) == 0 ) {
+ throw new ErrorPageError( 'tags-edit-nooldid-title', 'tags-edit-nooldid-text' );
+ }
+
+ $this->typeName = $request->getVal( 'type' );
+ $this->targetObj = Title::newFromText( $request->getText( 'target' ) );
+
+ // sanity check of parameter
+ switch ( $this->typeName ) {
+ case 'logentry':
+ case 'logging':
+ $this->typeName = 'logentry';
+ break;
+ default:
+ $this->typeName = 'revision';
+ break;
+ }
+
+ // Allow the list type to adjust the passed target
+ // Yuck! Copied straight out of SpecialRevisiondelete, but it does exactly
+ // what we want
+ $this->targetObj = RevisionDeleter::suggestTarget(
+ $this->typeName === 'revision' ? 'revision' : 'logging',
+ $this->targetObj,
+ $this->ids
+ );
+
+ $this->isAllowed = $user->isAllowed( 'changetags' );
+
+ $this->reason = $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
+ $this->showConvenienceLinks();
+
+ // Either submit or create our form
+ if ( $this->isAllowed && $this->submitClicked ) {
+ $this->submit( $request );
+ } else {
+ $this->showForm();
+ }
+
+ // Show relevant lines from the tag log
+ $tagLogPage = new LogPage( 'tag' );
+ $output->addHTML( "<h2>" . $tagLogPage->getName()->escaped() . "</h2>\n" );
+ LogEventsList::showLogExtract(
+ $output,
+ 'tag',
+ $this->targetObj,
+ '', /* user */
+ array( 'lim' => 25, 'conds' => array(), 'useMaster' => $this->wasSaved )
+ );
+ }
+
+ /**
+ * Show some useful links in the subtitle
+ */
+ 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' ),
+ $this->msg( 'viewpagelogs' )->escaped(),
+ array(),
+ array(
+ 'page' => $this->targetObj->getPrefixedText(),
+ 'hide_tag_log' => '0',
+ )
+ );
+ if ( !$this->targetObj->isSpecialPage() ) {
+ // Give a link to the page history
+ $links[] = Linker::linkKnown(
+ $this->targetObj,
+ $this->msg( 'pagehist' )->escaped(),
+ array(),
+ array( 'action' => 'history' )
+ );
+ }
+ // Link to Special:Tags
+ $links[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Tags' ),
+ $this->msg( 'tags-edit-manage-link' )->escaped()
+ );
+ // Logs themselves don't have histories or archived revisions
+ $this->getOutput()->addSubtitle( $this->getLanguage()->pipeList( $links ) );
+ }
+ }
+
+ /**
+ * Get the list object for this request
+ * @return ChangeTagsList
+ */
+ protected function getList() {
+ if ( is_null( $this->revList ) ) {
+ $this->revList = ChangeTagsList::factory( $this->typeName, $this->getContext(),
+ $this->targetObj, $this->ids );
+ }
+
+ return $this->revList;
+ }
+
+ /**
+ * Show a list of items that we will operate on, and show a form which allows
+ * the user to modify the tags applied to those items.
+ */
+ protected function showForm() {
+ $userAllowed = true;
+
+ $out = $this->getOutput();
+ // Messages: tags-edit-revision-selected, tags-edit-logentry-selected
+ $out->wrapWikiMsg( "<strong>$1</strong>", array(
+ "tags-edit-{$this->typeName}-selected",
+ $this->getLanguage()->formatNum( count( $this->ids ) ),
+ $this->targetObj->getPrefixedText()
+ ) );
+
+ $this->addHelpLink( 'Help:Tags' );
+ $out->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();
+ $numRevisions++;
+ $out->addHTML( $item->getHTML() );
+ }
+
+ if ( !$numRevisions ) {
+ throw new ErrorPageError( 'tags-edit-nooldid-title', 'tags-edit-nooldid-text' );
+ }
+
+ $out->addHTML( "</ul>" );
+ // Explanation text
+ $out->wrapWikiMsg( '<p>$1</p>', "tags-edit-{$this->typeName}-explanation" );
+
+ // Show form if the user can submit
+ if ( $this->isAllowed ) {
+ $form = Xml::openElement( 'form', array( 'method' => 'post',
+ 'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ),
+ 'id' => 'mw-revdel-form-revisions' ) ) .
+ Xml::fieldset( $this->msg( "tags-edit-{$this->typeName}-legend",
+ count( $this->ids ) )->text() ) .
+ $this->buildCheckBoxes() .
+ Xml::openElement( 'table' ) .
+ "<tr>\n" .
+ '<td class="mw-label">' .
+ Xml::label( $this->msg( 'tags-edit-reason' )->text(), 'wpReason' ) .
+ '</td>' .
+ '<td class="mw-input">' .
+ Xml::input(
+ 'wpReason',
+ 60,
+ $this->reason,
+ array( 'id' => 'wpReason', 'maxlength' => 100 )
+ ) .
+ '</td>' .
+ "</tr><tr>\n" .
+ '<td></td>' .
+ '<td class="mw-submit">' .
+ Xml::submitButton( $this->msg( "tags-edit-{$this->typeName}-submit",
+ $numRevisions )->text(), array( 'name' => 'wpSubmit' ) ) .
+ '</td>' .
+ "</tr>\n" .
+ Xml::closeElement( 'table' ) .
+ Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
+ Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
+ Html::hidden( 'type', $this->typeName ) .
+ Html::hidden( 'ids', implode( ',', $this->ids ) ) .
+ Xml::closeElement( 'fieldset' ) . "\n" .
+ Xml::closeElement( 'form' ) . "\n";
+ } else {
+ $form = '';
+ }
+ $out->addHTML( $form );
+ }
+
+ /**
+ * @return string HTML
+ */
+ protected function buildCheckBoxes() {
+ // If there is just one item, provide the user with a multi-select field
+ $list = $this->getList();
+ if ( $list->length() == 1 ) {
+ $list->reset();
+ $tags = $list->current()->getTags();
+ if ( $tags ) {
+ $tags = explode( ',', $tags );
+ } else {
+ $tags = array();
+ }
+
+ $html = '<table id="mw-edittags-tags-selector">';
+ $html .= '<tr><td>' . $this->msg( 'tags-edit-existing-tags' )->escaped() .
+ '</td><td>';
+ if ( $tags ) {
+ $html .= $this->getLanguage()->commaList( array_map( 'htmlspecialchars', $tags ) );
+ } else {
+ $html .= $this->msg( 'tags-edit-existing-tags-none' )->parse();
+ }
+ $html .= '</td></tr>';
+ $tagSelect = $this->getTagSelect( $tags, $this->msg( 'tags-edit-new-tags' )->plain() );
+ $html .= '<tr><td>' . $tagSelect[0] . '</td><td>' . $tagSelect[1];
+ // also output the tags currently applied as a hidden form field, so we
+ // know what to remove from the revision/log entry when the form is submitted
+ $html .= Html::hidden( 'wpExistingTags', implode( ',', $tags ) );
+ $html .= '</td></tr></table>';
+ } else {
+ // Otherwise, use a multi-select field for adding tags, and a list of
+ // checkboxes for removing them
+ $tags = array();
+
+ // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
+ for ( $list->reset(); $list->current(); $list->next() ) {
+ // @codingStandardsIgnoreEnd
+ $currentTags = $list->current()->getTags();
+ if ( $currentTags ) {
+ $tags = array_merge( $tags, explode( ',', $currentTags ) );
+ }
+ }
+ $tags = array_unique( $tags );
+
+ $html = '<table id="mw-edittags-tags-selector-multi"><tr><td>';
+ $tagSelect = $this->getTagSelect( array(), $this->msg( 'tags-edit-add' )->plain() );
+ $html .= '<p>' . $tagSelect[0] . '</p>' . $tagSelect[1] . '</td><td>';
+ $html .= Xml::element( 'p', null, $this->msg( 'tags-edit-remove' )->plain() );
+ $html .= Xml::checkLabel( $this->msg( 'tags-edit-remove-all-tags' )->plain(),
+ 'wpRemoveAllTags', 'mw-edittags-remove-all' );
+ $i = 0; // used for generating checkbox IDs only
+ foreach ( $tags as $tag ) {
+ $html .= Xml::element( 'br' ) . "\n" . Xml::checkLabel( $tag,
+ 'wpTagsToRemove[]', 'mw-edittags-remove-' . $i++, false, array(
+ 'value' => $tag,
+ 'class' => 'mw-edittags-remove-checkbox',
+ ) );
+ }
+ $html .= '</td></tr></table>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Returns a <select multiple> element with a list of change tags that can be
+ * applied by users.
+ *
+ * @param array $selectedTags The tags that should be preselected in the
+ * list. Any tags in this list, but not in the list returned by
+ * ChangeTags::listExplicitlyDefinedTags, will be appended to the <select>
+ * element.
+ * @param string $label The text of a <label> to precede the <select>
+ * @return array HTML <label> element at index 0, HTML <select> element at
+ * index 1
+ */
+ protected function getTagSelect( $selectedTags, $label ) {
+ $result = array();
+ $result[0] = Xml::label( $label, 'mw-edittags-tag-list' );
+ $result[1] = Xml::openElement( 'select', array(
+ 'name' => 'wpTagList[]',
+ 'id' => 'mw-edittags-tag-list',
+ 'multiple' => 'multiple',
+ 'size' => '8',
+ ) );
+
+ $tags = ChangeTags::listExplicitlyDefinedTags();
+ $tags = array_unique( array_merge( $tags, $selectedTags ) );
+ foreach ( $tags as $tag ) {
+ $result[1] .= Xml::option( $tag, $tag, in_array( $tag, $selectedTags ) );
+ }
+
+ $result[1] .= Xml::closeElement( 'select' );
+ return $result;
+ }
+
+ /**
+ * UI entry point for form submission.
+ * @throws PermissionsError
+ * @return bool
+ */
+ protected function submit() {
+ // Check edit token on submission
+ $request = $this->getRequest();
+ $token = $request->getVal( 'wpEditToken' );
+ if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
+ $this->getOutput()->addWikiMsg( 'sessionfailure' );
+ return false;
+ }
+
+ // Evaluate incoming request data
+ $tagList = $request->getArray( 'wpTagList' );
+ if ( is_null( $tagList ) ) {
+ $tagList = array();
+ }
+ $existingTags = $request->getVal( 'wpExistingTags' );
+ if ( is_null( $existingTags ) || $existingTags === '' ) {
+ $existingTags = array();
+ } else {
+ $existingTags = explode( ',', $existingTags );
+ }
+
+ if ( count( $this->ids ) > 1 ) {
+ // multiple revisions selected
+ $tagsToAdd = $tagList;
+ if ( $request->getBool( 'wpRemoveAllTags' ) ) {
+ $tagsToRemove = $existingTags;
+ } else {
+ $tagsToRemove = $request->getArray( 'wpTagsToRemove' );
+ }
+ } else {
+ // single revision selected
+ // The user tells us which tags they want associated to the revision.
+ // We have to figure out which ones to add, and which to remove.
+ $tagsToAdd = array_diff( $tagList, $existingTags );
+ $tagsToRemove = array_diff( $existingTags, $tagList );
+ }
+
+ if ( !$tagsToAdd && !$tagsToRemove ) {
+ $status = Status::newFatal( 'tags-edit-none-selected' );
+ } else {
+ $status = $this->getList()->updateChangeTagsOnAll( $tagsToAdd,
+ $tagsToRemove, null, $this->reason, $this->getUser() );
+ }
+
+ if ( $status->isGood() ) {
+ $this->success();
+ return true;
+ } else {
+ $this->failure( $status );
+ return false;
+ }
+ }
+
+ /**
+ * Report that the submit operation succeeded
+ */
+ protected function success() {
+ $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
+ $this->getOutput()->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>",
+ 'tags-edit-success' );
+ $this->wasSaved = true;
+ $this->revList->reloadFromMaster();
+ $this->reason = ''; // no need to spew the reason back at the user
+ $this->showForm();
+ }
+
+ /**
+ * Report that the submit operation failed
+ * @param Status $status
+ */
+ protected function failure( $status ) {
+ $this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
+ $this->getOutput()->addWikiText( '<div class="errorbox">' .
+ $status->getWikiText( 'tags-edit-failure' ) .
+ '</div>'
+ );
+ $this->showForm();
+ }
+
+ public function getDescription() {
+ return $this->msg( 'tags-edit-title' )->text();
+ }
+
+ protected function getGroupName() {
+ return 'pagetools';
+ }
+}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
index 3656b9cc..910fe259 100644
--- a/includes/specials/SpecialEditWatchlist.php
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -134,22 +134,17 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
/**
- * Return an array of subpages beginning with $search that this special page will accept.
+ * Return an array of subpages 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
+ * @see also SpecialWatchlist::getSubpagesForPrefixSearch
+ * @return string[] 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',
- )
+ public function getSubpagesForPrefixSearch() {
+ // SpecialWatchlist uses SpecialEditWatchlist::getMode, so new types should be added
+ // here and there - no 'edit' here, because that the default for this page
+ return array(
+ 'clear',
+ 'raw',
);
}
@@ -261,7 +256,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
// Do a batch existence check
$batch = new LinkBatch();
if ( count( $titles ) >= 100 ) {
- $output = wfMessage( 'watchlistedit-too-many' )->parse();
+ $output = $this->msg( 'watchlistedit-too-many' )->parse();
return;
}
foreach ( $titles as $title ) {
@@ -349,7 +344,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
*/
protected function getWatchlistInfo() {
$titles = array();
- $dbr = wfGetDB( DB_MASTER );
+ $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
array( 'watchlist' ),
@@ -518,7 +513,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
);
$page = WikiPage::factory( $title );
- wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) );
+ Hooks::run( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) );
}
}
}
@@ -556,7 +551,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
// Allow subscribers to manipulate the list of watched pages (or use it
// to preload lots of details at once)
$watchlistInfo = $this->getWatchlistInfo();
- wfRunHooks(
+ Hooks::run(
'WatchlistEditorBeforeFormRender',
array( &$watchlistInfo )
);
@@ -609,6 +604,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$context->setTitle( $this->getPageTitle() ); // Remove subpage
$form = new EditWatchlistNormalHTMLForm( $fields, $context );
$form->setSubmitTextMsg( 'watchlistedit-normal-submit' );
+ $form->setSubmitDestructive();
# Used message keys:
# 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
$form->setSubmitTooltip( 'watchlistedit-normal-submit' );
@@ -628,7 +624,10 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
private function buildRemoveLine( $title ) {
$link = Linker::link( $title );
- $tools['talk'] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() );
+ $tools['talk'] = Linker::link(
+ $title->getTalkPage(),
+ $this->msg( 'talkpagelinktext' )->escaped()
+ );
if ( $title->exists() ) {
$tools['history'] = Linker::linkKnown(
@@ -646,7 +645,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
);
}
- wfRunHooks(
+ Hooks::run(
'WatchlistEditorBuildRemoveLine',
array( &$tools, $title, $title->isRedirect(), $this->getSkin(), &$link )
);
@@ -701,6 +700,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$form->setWrapperLegendMsg( 'watchlistedit-clear-legend' );
$form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() );
$form->setSubmitCallback( array( $this, 'submitClear' ) );
+ $form->setSubmitDestructive();
return $form;
}
@@ -760,7 +760,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
return Html::rawElement(
'span',
array( 'class' => 'mw-watchlist-toollinks' ),
- wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text()
+ wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $tools ) )->escaped()
);
}
}
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 20532a92..c55fa94c 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -160,7 +160,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$form->setWrapperLegendMsg( 'email-legend' );
$form->loadData();
- if ( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) {
+ if ( !Hooks::run( 'EmailUserForm', array( &$form ) ) ) {
return;
}
@@ -243,8 +243,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$hookErr = false;
- wfRunHooks( 'UserCanSendEmail', array( &$user, &$hookErr ) );
- wfRunHooks( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) );
+ Hooks::run( 'UserCanSendEmail', array( &$user, &$hookErr ) );
+ Hooks::run( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) );
if ( $hookErr ) {
return $hookErr;
@@ -324,7 +324,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$from->name, $to->name )->inContentLanguage()->text();
$error = '';
- if ( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) {
+ if ( !Hooks::run( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) {
return $error;
}
@@ -367,12 +367,12 @@ class SpecialEmailUser extends UnlistedSpecialPage {
if ( $data['CCMe'] && $to != $from ) {
$cc_subject = $context->msg( 'emailccsubject' )->rawParams(
$target->getName(), $subject )->text();
- wfRunHooks( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) );
+ Hooks::run( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) );
$ccStatus = UserMailer::send( $from, $from, $cc_subject, $text );
$status->merge( $ccStatus );
}
- wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $text ) );
+ Hooks::run( 'EmailUserComplete', array( $to, $from, $subject, $text ) );
return $status;
}
diff --git a/includes/specials/SpecialExpandTemplates.php b/includes/specials/SpecialExpandTemplates.php
index 62f957fc..b7582e6c 100644
--- a/includes/specials/SpecialExpandTemplates.php
+++ b/includes/specials/SpecialExpandTemplates.php
@@ -77,7 +77,7 @@ class SpecialExpandTemplates extends SpecialPage {
$options->setMaxIncludeSize( self::MAX_INCLUDE_SIZE );
if ( $this->generateXML ) {
- $wgParser->startExternalParse( $title, $options, OT_PREPROCESS );
+ $wgParser->startExternalParse( $title, $options, Parser::OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $input );
if ( method_exists( $dom, 'saveXML' ) ) {
@@ -154,7 +154,7 @@ class SpecialExpandTemplates extends SpecialPage {
'contexttitle',
60,
$title,
- array( 'autofocus' => true )
+ array( 'autofocus' => '', 'class' => 'mw-ui-input-inline' )
) . '</p>';
$form .= '<p>' . Xml::label(
$this->msg( 'expand_templates_input' )->text(),
@@ -278,6 +278,7 @@ class SpecialExpandTemplates extends SpecialPage {
) ) );
$out->addParserOutputContent( $pout );
$out->addHTML( Html::closeElement( 'div' ) );
+ $out->setCategoryLinks( $pout->getCategories() );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index 38c52a01..c30d962a 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -182,13 +182,20 @@ class SpecialExport extends SpecialPage {
$out = $this->getOutput();
$out->addWikiMsg( 'exporttext' );
+ if ( $page == '' ) {
+ $categoryName = $request->getText( 'catname' );
+ } else {
+ $categoryName = '';
+ }
+
$form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ) ) );
$form .= Xml::inputLabel(
$this->msg( 'export-addcattext' )->text(),
'catname',
'catname',
- 40
+ 40,
+ $categoryName
) . '&#160;';
$form .= Xml::submitButton(
$this->msg( 'export-addcat' )->text(),
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index fc26c903..da79bb81 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -110,25 +110,31 @@ class FileDuplicateSearchPage extends QueryPage {
$out = $this->getOutput();
# Create the input form
- $out->addHTML(
- Html::openElement(
- 'form',
- array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => wfScript() )
- ) . "\n" .
- Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n" .
- Html::openElement( 'fieldset' ) . "\n" .
- Html::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) . "\n" .
- Xml::inputLabel(
- $this->msg( 'fileduplicatesearch-filename' )->text(),
- 'filename',
- 'filename',
- 50,
- $this->filename
- ) . "\n" .
- Xml::submitButton( $this->msg( 'fileduplicatesearch-submit' )->text() ) . "\n" .
- Html::closeElement( 'fieldset' ) . "\n" .
- Html::closeElement( 'form' )
+ $formFields = array(
+ 'filename' => array(
+ 'type' => 'text',
+ 'name' => 'filename',
+ 'label-message' => 'fileduplicatesearch-filename',
+ 'id' => 'filename',
+ 'size' => 50,
+ 'value' => $this->filename,
+ 'cssclass' => 'mw-ui-input-inline'
+ ),
+ );
+ $hiddenFields = array(
+ 'title' => $this->getPageTitle()->getPrefixedDBKey(),
);
+ $htmlForm = HTMLForm::factory( 'inline', $formFields, $this->getContext() );
+ $htmlForm->addHiddenFields( $hiddenFields );
+ $htmlForm->setAction( wfScript() );
+ $htmlForm->setMethod( 'get' );
+ $htmlForm->setSubmitProgressive();
+ $htmlForm->setSubmitTextMsg( $this->msg( 'fileduplicatesearch-submit' ) );
+ $htmlForm->setWrapperLegendMsg( 'fileduplicatesearch-legend' );
+
+ // The form should be visible always, even if it was submitted (e.g. to perform another action).
+ // To bypass the callback validation of HTMLForm, use prepareForm() and displayForm().
+ $htmlForm->prepareForm()->displayForm( false );
if ( $this->file ) {
$this->hash = $this->file->getSha1();
@@ -196,7 +202,7 @@ class FileDuplicateSearchPage extends QueryPage {
*
* @param Skin $skin
* @param File $result
- * @return string
+ * @return string HTML
*/
function formatResult( $skin, $result ) {
global $wgContLang;
@@ -204,15 +210,14 @@ class FileDuplicateSearchPage extends QueryPage {
$nt = $result->getTitle();
$text = $wgContLang->convert( $nt->getText() );
$plink = Linker::link(
- Title::newFromText( $nt->getPrefixedText() ),
- $text
+ $nt,
+ htmlspecialchars( $text )
);
$userText = $result->getUser( 'text' );
if ( $result->isLocal() ) {
$userId = $result->getUser( 'id' );
$user = Linker::userLink( $userId, $userText );
- $user .= $this->getContext()->msg( 'word-separator' )->plain();
$user .= '<span style="white-space: nowrap;">';
$user .= Linker::userToolLinks( $userId, $userText );
$user .= '</span>';
@@ -220,7 +225,8 @@ class FileDuplicateSearchPage extends QueryPage {
$user = htmlspecialchars( $userText );
}
- $time = $this->getLanguage()->userTimeAndDate( $result->getTimestamp(), $this->getUser() );
+ $time = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
+ $result->getTimestamp(), $this->getUser() ) );
return "$plink . . $user . . $time";
}
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index 5860f636..93232117 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -2,7 +2,6 @@
/**
* Implements Special:Filepath
*
- * @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
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index eab4784c..af869647 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -30,6 +30,7 @@
* @ingroup SpecialPage
*/
class SpecialImport extends SpecialPage {
+ private $sourceName = false;
private $interwiki = false;
private $subproject;
private $fullInterwikiPrefix;
@@ -46,17 +47,20 @@ class SpecialImport extends SpecialPage {
*/
public function __construct() {
parent::__construct( 'Import', 'import' );
- $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
}
/**
* Execute
* @param string|null $par
+ * @throws PermissionsError
+ * @throws ReadOnlyError
*/
function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
+ $this->namespace = $this->getConfig()->get( 'ImportTargetNamespace' );
+
$this->getOutput()->addModules( 'mediawiki.special.import' );
$user = $this->getUser();
@@ -98,7 +102,7 @@ class SpecialImport extends SpecialPage {
$isUpload = false;
$request = $this->getRequest();
$this->namespace = $request->getIntOrNull( 'namespace' );
- $sourceName = $request->getVal( "source" );
+ $this->sourceName = $request->getVal( "source" );
$this->logcomment = $request->getText( 'log-comment' );
$this->pageLinkDepth = $this->getConfig()->get( 'ExportMaxLinkDepth' ) == 0
@@ -109,14 +113,14 @@ class SpecialImport extends SpecialPage {
$user = $this->getUser();
if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) {
$source = Status::newFatal( 'import-token-mismatch' );
- } elseif ( $sourceName == 'upload' ) {
+ } elseif ( $this->sourceName == 'upload' ) {
$isUpload = true;
if ( $user->isAllowed( 'importupload' ) ) {
$source = ImportStreamSource::newFromUpload( "xmlimport" );
} else {
throw new PermissionsError( 'importupload' );
}
- } elseif ( $sourceName == "interwiki" ) {
+ } elseif ( $this->sourceName == "interwiki" ) {
if ( !$user->isAllowed( 'import' ) ) {
throw new PermissionsError( 'import' );
}
@@ -156,7 +160,7 @@ class SpecialImport extends SpecialPage {
array( 'importfailed', $source->getWikiText() )
);
} else {
- $importer = new WikiImporter( $source->value );
+ $importer = new WikiImporter( $source->value, $this->getConfig() );
if ( !is_null( $this->namespace ) ) {
$importer->setTargetNamespace( $this->namespace );
}
@@ -190,7 +194,7 @@ class SpecialImport extends SpecialPage {
$reporter->open();
try {
$importer->doImport();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$exception = $e;
}
$result = $reporter->close();
@@ -250,7 +254,8 @@ class SpecialImport extends SpecialPage {
Xml::label( $this->msg( 'import-comment' )->text(), 'mw-import-comment' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'log-comment', 50, '',
+ Xml::input( 'log-comment', 50,
+ ( $this->sourceName == 'upload' ? $this->logcomment : '' ),
array( 'id' => 'mw-import-comment', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
@@ -430,7 +435,8 @@ class SpecialImport extends SpecialPage {
Xml::label( $this->msg( 'import-comment' )->text(), 'mw-interwiki-comment' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'log-comment', 50, '',
+ Xml::input( 'log-comment', 50,
+ ( $this->sourceName == 'interwiki' ? $this->logcomment : '' ),
array( 'id' => 'mw-interwiki-comment', 'type' => 'text' ) ) . ' ' .
"</td>
</tr>
@@ -515,13 +521,14 @@ class ImportReporter extends ContextSource {
/**
* @param Title $title
- * @param Title $origTitle
+ * @param ForeignTitle $foreignTitle
* @param int $revisionCount
* @param int $successCount
* @param array $pageInfo
* @return void
*/
- function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
+ function reportPage( $title, $foreignTitle, $revisionCount,
+ $successCount, $pageInfo ) {
$args = func_get_args();
call_user_func_array( $this->mOriginalPageOutCallback, $args );
@@ -539,7 +546,6 @@ class ImportReporter extends ContextSource {
"</li>\n"
);
- $log = new LogPage( 'import' );
if ( $this->mIsUpload ) {
$detail = $this->msg( 'import-logentry-upload-detail' )->numParams(
$successCount )->inContentLanguage()->text();
@@ -547,19 +553,26 @@ class ImportReporter extends ContextSource {
$detail .= $this->msg( 'colon-separator' )->inContentLanguage()->text()
. $this->reason;
}
- $log->addEntry( 'upload', $title, $detail, array(), $this->getUser() );
+ $action = 'upload';
} else {
$interwiki = '[[:' . $this->mInterwiki . ':' .
- $origTitle->getPrefixedText() . ']]';
+ $foreignTitle->getFullText() . ']]';
$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;
}
- $log->addEntry( 'interwiki', $title, $detail, array(), $this->getUser() );
+ $action = 'interwiki';
}
+ $logEntry = new ManualLogEntry( 'import', $action );
+ $logEntry->setTarget( $title );
+ $logEntry->setComment( $detail );
+ $logEntry->setPerformer( $this->getUser() );
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
+
$comment = $detail; // quick
$dbw = wfGetDB( DB_MASTER );
$latest = $title->getLatestRevID();
@@ -576,7 +589,7 @@ class ImportReporter extends ContextSource {
$page = WikiPage::factory( $title );
# Update page record
$page->updateRevisionOn( $dbw, $nullRevision );
- wfRunHooks(
+ Hooks::run(
'NewRevisionFromEditComplete',
array( $page, $nullRevision, $latest, $this->getUser() )
);
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php
index 7d745a50..ecb166a4 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -150,12 +150,11 @@ class SpecialJavaScriptTest extends SpecialPage {
*/
private function viewQUnit() {
$out = $this->getOutput();
- $testConfig = $this->getConfig()->get( 'JavaScriptTestConfig' );
$modules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
$summary = $this->msg( 'javascripttest-qunit-intro' )
- ->params( $testConfig['qunit']['documentation'] )
+ ->params( 'https://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing' )
->parseAsBlock();
$baseHtml = <<<HTML
@@ -164,13 +163,6 @@ class SpecialJavaScriptTest extends SpecialPage {
</div>
HTML;
- // Used in ./tests/qunit/data/testrunner.js, see also documentation of
- // $wgJavaScriptTestConfig in DefaultSettings.php
- $out->addJsConfigVars(
- 'QUnitTestSwarmInjectJSPath',
- $testConfig['qunit']['testswarm-injectjs']
- );
-
$out->addHtml( $this->wrapSummaryHtml( $summary ) . $baseHtml );
// The testrunner configures QUnit and essentially depends on it. However, test suites
@@ -200,7 +192,6 @@ HTML;
*/
private function exportQUnit() {
$out = $this->getOutput();
-
$out->disable();
$rl = $out->getResourceLoader();
@@ -251,9 +242,13 @@ HTML;
'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
) );
- $styles = $out->makeResourceLoaderLink( 'jquery.qunit', ResourceLoaderModule::TYPE_STYLES, false );
+ $styles = $out->makeResourceLoaderLink(
+ 'jquery.qunit', ResourceLoaderModule::TYPE_STYLES, false
+ );
// Use 'raw' since this is a plain HTML page without ResourceLoader
- $scripts = $out->makeResourceLoaderLink( 'jquery.qunit', ResourceLoaderModule::TYPE_SCRIPTS, false, array( 'raw' => 'true' ) );
+ $scripts = $out->makeResourceLoaderLink(
+ 'jquery.qunit', ResourceLoaderModule::TYPE_SCRIPTS, false, array( 'raw' => 'true' )
+ );
$head = trim( $styles['html'] . $scripts['html'] );
$html = <<<HTML
@@ -269,18 +264,12 @@ HTML;
}
/**
- * Return an array of subpages beginning with $search that this special page will accept.
+ * Return an array of subpages 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
+ * @return string[] subpages
*/
- public function prefixSearchSubpages( $search, $limit = 10 ) {
- return self::prefixSearchArray(
- $search,
- $limit,
- self::$frameworks
- );
+ public function getSubpagesForPrefixSearch() {
+ return self::$frameworks;
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 371469bb..75ff8f30 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -27,6 +27,8 @@
* @ingroup SpecialPage
*/
class LinkSearchPage extends QueryPage {
+ /** @var array|bool */
+ private $mungedQuery = false;
/**
* @var PageLinkRenderer
@@ -66,8 +68,9 @@ class LinkSearchPage extends QueryPage {
* This allows for dependency injection even though we don't control object creation.
*/
private function initServices() {
+ global $wgLanguageCode;
if ( !$this->linkRenderer ) {
- $lang = $this->getContext()->getLanguage();
+ $lang = Language::factory( $wgLanguageCode );
$titleFormatter = new MediaWikiTitleCodec( $lang, GenderCache::singleton() );
$this->linkRenderer = new MediaWikiPageLinkRenderer( $titleFormatter );
}
@@ -88,7 +91,7 @@ class LinkSearchPage extends QueryPage {
$request = $this->getRequest();
$target = $request->getVal( 'target', $par );
- $namespace = $request->getIntorNull( 'namespace', null );
+ $namespace = $request->getIntOrNull( 'namespace', null );
$protocols_list = array();
foreach ( $this->getConfig()->get( 'UrlProtocols' ) as $prot ) {
@@ -162,7 +165,7 @@ class LinkSearchPage extends QueryPage {
'namespace' => $namespace,
'protocol' => $protocol ) );
parent::execute( $par );
- if ( $this->mMungedQuery === false ) {
+ if ( $this->mungedQuery === false ) {
$out->addWikiMsg( 'linksearch-error' );
}
}
@@ -221,13 +224,13 @@ class LinkSearchPage extends QueryPage {
$dbr = wfGetDB( DB_SLAVE );
// strip everything past first wildcard, so that
// index-based-only lookup would be done
- list( $this->mMungedQuery, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
- if ( $this->mMungedQuery === false ) {
+ list( $this->mungedQuery, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
+ if ( $this->mungedQuery === false ) {
// Invalid query; return no results
return array( 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' );
}
- $stripped = LinkFilter::keepOneWildcard( $this->mMungedQuery );
+ $stripped = LinkFilter::keepOneWildcard( $this->mungedQuery );
$like = $dbr->buildLike( $stripped );
$retval = array(
'tables' => array( 'page', 'externallinks' ),
@@ -252,6 +255,25 @@ class LinkSearchPage extends QueryPage {
}
/**
+ * Pre-fill the link cache
+ *
+ * @param IDatabase $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
@@ -267,24 +289,6 @@ 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 );
- if ( $this->mMungedQuery === false ) {
- $this->getOutput()->addWikiMsg( 'linksearch-error' );
- } else {
- // For debugging
- // Generates invalid xhtml with patterns that contain --
- //$this->getOutput()->addHTML( "\n<!-- " . htmlspecialchars( $this->mMungedQuery ) . " -->\n" );
- parent::doQuery( $offset, $limit );
- }
- }
-
- /**
* Override to squash the ORDER BY.
* We do a truncated index search, so the optimizer won't trust
* it as good enough for optimizing sort. The implicit ordering
diff --git a/includes/specials/SpecialListDuplicatedFiles.php b/includes/specials/SpecialListDuplicatedFiles.php
index 26672706..1e3dff6f 100644
--- a/includes/specials/SpecialListDuplicatedFiles.php
+++ b/includes/specials/SpecialListDuplicatedFiles.php
@@ -71,7 +71,7 @@ class ListDuplicatedFilesPage extends QueryPage {
/**
* Pre-fill the link cache
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index 04a83c8f..d4b45fb3 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -87,7 +87,7 @@ class ImageListPager extends TablePager {
$this->mIncluding = $including;
$this->mShowAll = $showAll;
- if ( $userName ) {
+ if ( $userName !== null && $userName !== '' ) {
$nt = Title::newFromText( $userName, NS_USER );
if ( !is_null( $nt ) ) {
$this->mUserName = $nt->getText();
@@ -203,7 +203,9 @@ class ImageListPager extends TablePager {
} else {
return false;
}
- } elseif ( $this->getConfig()->get( 'MiserMode' ) && $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;
@@ -300,6 +302,7 @@ class ImageListPager extends TablePager {
* @param int $limit
* @param bool $asc
* @return array
+ * @throws MWException
*/
function reallyDoQuery( $offset, $limit, $asc ) {
$prevTableName = $this->mTableName;
@@ -422,7 +425,7 @@ class ImageListPager extends TablePager {
function formatValue( $field, $value ) {
switch ( $field ) {
case 'thumb':
- $opt = array( 'time' => $this->mCurrentRow->img_timestamp );
+ $opt = array( 'time' => wfTimestamp( TS_MW, $this->mCurrentRow->img_timestamp ) );
$file = RepoGroup::singleton()->getLocalRepo()->findFile( $value, $opt );
// If statement for paranoia
if ( $file ) {
@@ -519,6 +522,7 @@ class ImageListPager extends TablePager {
);
}
+ $this->getOutput()->addModules( 'mediawiki.userSuggest' );
$fields['user'] = array(
'type' => 'text',
'name' => 'user',
@@ -527,6 +531,7 @@ class ImageListPager extends TablePager {
'default' => $this->mUserName,
'size' => '40',
'maxlength' => '255',
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
);
$fields['ilshowall'] = array(
@@ -541,6 +546,7 @@ class ImageListPager extends TablePager {
unset( $query['title'] );
unset( $query['limit'] );
unset( $query['ilsearch'] );
+ unset( $query['ilshowall'] );
unset( $query['user'] );
$form = new HTMLForm( $fields, $this->getContext() );
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index 5bae28f0..828a93b9 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -86,13 +86,14 @@ class SpecialListGroupRights extends SpecialPage {
$grouppageLocalized = !$msg->isBlank() ?
$msg->text() :
MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname;
+ $grouppageLocalizedTitle = Title::newFromText( $grouppageLocalized );
- if ( $group == '*' ) {
- // Do not make a link for the generic * group
+ if ( $group == '*' || !$grouppageLocalizedTitle ) {
+ // Do not make a link for the generic * group or group with invalid group page
$grouppage = htmlspecialchars( $groupnameLocalized );
} else {
$grouppage = Linker::link(
- Title::newFromText( $grouppageLocalized ),
+ $grouppageLocalizedTitle,
htmlspecialchars( $groupnameLocalized )
);
}
@@ -235,20 +236,18 @@ class SpecialListGroupRights extends SpecialPage {
foreach ( $permissions as $permission => $granted ) {
//show as granted only if it isn't revoked to prevent duplicate display of permissions
if ( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) {
- $description = $this->msg( 'listgrouprights-right-display',
+ $r[] = $this->msg( 'listgrouprights-right-display',
User::getRightDescription( $permission ),
'<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
)->parse();
- $r[] = $description;
}
}
foreach ( $revoke as $permission => $revoked ) {
if ( $revoked ) {
- $description = $this->msg( 'listgrouprights-right-revoked',
+ $r[] = $this->msg( 'listgrouprights-right-revoked',
User::getRightDescription( $permission ),
'<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
)->parse();
- $r[] = $description;
}
}
@@ -257,51 +256,28 @@ class SpecialListGroupRights extends SpecialPage {
$lang = $this->getLanguage();
$allGroups = User::getAllGroups();
- if ( $add === true ) {
- $r[] = $this->msg( 'listgrouprights-addgroup-all' )->escaped();
- } 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 ) ) {
- $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 ) ) {
- $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();
- }
- }
+ $changeGroups = array(
+ 'addgroup' => $add,
+ 'removegroup' => $remove,
+ 'addgroup-self' => $addSelf,
+ 'removegroup-self' => $removeSelf
+ );
- if ( $removeSelf === true ) {
- $r[] = $this->msg( 'listgrouprights-removegroup-self-all' )->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();
+ foreach ( $changeGroups as $messageKey => $changeGroup ) {
+ if ( $changeGroup === true ) {
+ // For grep: listgrouprights-addgroup-all, listgrouprights-removegroup-all,
+ // listgrouprights-addgroup-self-all, listgrouprights-removegroup-self-all
+ $r[] = $this->msg( 'listgrouprights-' . $messageKey . '-all' )->escaped();
+ } elseif ( is_array( $changeGroup ) ) {
+ $changeGroup = array_intersect( array_values( array_unique( $changeGroup ) ), $allGroups );
+ if ( count( $changeGroup ) ) {
+ // For grep: listgrouprights-addgroup, listgrouprights-removegroup,
+ // listgrouprights-addgroup-self, listgrouprights-removegroup-self
+ $r[] = $this->msg( 'listgrouprights-' . $messageKey,
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $changeGroup ) ),
+ count( $changeGroup )
+ )->parse();
+ }
}
}
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index de05be41..2df48347 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -72,7 +72,7 @@ class ListredirectsPage extends QueryPage {
/**
* Cache page existence for performance
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index dad9074d..56c4eb50 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -35,6 +35,11 @@
class UsersPager extends AlphabeticPager {
/**
+ * @var array A array with user ids as key and a array of groups as value
+ */
+ protected $userGroupCache;
+
+ /**
* @param IContextSource $context
* @param array $par (Default null)
* @param bool $including Whether this page is being transcluded in
@@ -132,8 +137,6 @@ class UsersPager extends AlphabeticPager {
'user_name' => $this->creationSort ? 'MAX(user_name)' : 'user_name',
'user_id' => $this->creationSort ? 'user_id' : 'MAX(user_id)',
'edits' => 'MAX(user_editcount)',
- 'numgroups' => 'COUNT(ug_group)',
- 'singlegroup' => 'MAX(ug_group)', // the usergroup if there is only one
'creation' => 'MIN(user_registration)',
'ipb_deleted' => 'MAX(ipb_deleted)' // block/hide status
),
@@ -150,7 +153,7 @@ class UsersPager extends AlphabeticPager {
'conds' => $conds
);
- wfRunHooks( 'SpecialListusersQueryInfo', array( $this, &$query ) );
+ Hooks::run( 'SpecialListusersQueryInfo', array( $this, &$query ) );
return $query;
}
@@ -176,7 +179,7 @@ class UsersPager extends AlphabeticPager {
$lang = $this->getLanguage();
$groups = '';
- $groups_list = self::getGroups( $row->user_id );
+ $groups_list = self::getGroups( intval( $row->user_id ), $this->userGroupCache );
if ( !$this->including && count( $groups_list ) > 0 ) {
$list = array();
@@ -211,18 +214,45 @@ class UsersPager extends AlphabeticPager {
' ' . $this->msg( 'listusers-blocked', $userName )->escaped() :
'';
- wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) );
+ Hooks::run( 'SpecialListusersFormatRow', array( &$item, $row ) );
return Html::rawElement( 'li', array(), "{$item}{$edits}{$created}{$blocked}" );
}
function doBatchLookups() {
$batch = new LinkBatch();
+ $userIds = array();
# Give some pointers to make user links
foreach ( $this->mResult as $row ) {
$batch->add( NS_USER, $row->user_name );
$batch->add( NS_USER_TALK, $row->user_name );
+ $userIds[] = $row->user_id;
+ }
+
+ // Lookup groups for all the users
+ $dbr = wfGetDB( DB_SLAVE );
+ $groupRes = $dbr->select(
+ 'user_groups',
+ array( 'ug_user', 'ug_group' ),
+ array( 'ug_user' => $userIds ),
+ __METHOD__
+ );
+ $cache = array();
+ $groups = array();
+ foreach ( $groupRes as $row ) {
+ $cache[intval( $row->ug_user )][] = $row->ug_group;
+ $groups[$row->ug_group] = true;
}
+ $this->userGroupCache = $cache;
+
+ // Add page of groups to link batch
+ foreach ( $groups as $group => $unused ) {
+ $groupPage = User::getGroupPage( $group );
+ if ( $groupPage ) {
+ $batch->addObj( $groupPage );
+ }
+ }
+
$batch->execute();
$this->mResult->rewind();
}
@@ -284,12 +314,12 @@ class UsersPager extends AlphabeticPager {
);
$out .= '<br />';
- wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) );
+ Hooks::run( 'SpecialListusersHeaderForm', array( $this, &$out ) );
# Submit button and form bottom
$out .= Html::hidden( 'limit', $this->mLimit );
$out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() );
- wfRunHooks( 'SpecialListusersHeader', array( $this, &$out ) );
+ Hooks::run( 'SpecialListusersHeader', array( $this, &$out ) );
$out .= Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' );
@@ -322,7 +352,7 @@ class UsersPager extends AlphabeticPager {
if ( $this->requestedUser != '' ) {
$query['username'] = $this->requestedUser;
}
- wfRunHooks( 'SpecialListusersDefaultQuery', array( $this, &$query ) );
+ Hooks::run( 'SpecialListusersDefaultQuery', array( $this, &$query ) );
return $query;
}
@@ -331,11 +361,17 @@ class UsersPager extends AlphabeticPager {
* Get a list of groups the specified user belongs to
*
* @param int $uid User id
+ * @param array|null $cache
* @return array
*/
- protected static function getGroups( $uid ) {
- $user = User::newFromId( $uid );
- $groups = array_diff( $user->getEffectiveGroups(), User::getImplicitGroups() );
+ protected static function getGroups( $uid, $cache = null ) {
+ if ( $cache === null ) {
+ $user = User::newFromId( $uid );
+ $effectiveGroups = $user->getEffectiveGroups();
+ } else {
+ $effectiveGroups = isset( $cache[$uid] ) ? $cache[$uid] : array();
+ }
+ $groups = array_diff( $effectiveGroups, User::getImplicitGroups() );
return $groups;
}
@@ -350,7 +386,7 @@ class UsersPager extends AlphabeticPager {
protected static function buildGroupLink( $group, $username ) {
return User::makeGroupLinkHtml(
$group,
- htmlspecialchars( User::getGroupMember( $group, $username ) )
+ User::getGroupMember( $group, $username )
);
}
}
@@ -397,15 +433,12 @@ class SpecialListUsers extends IncludableSpecialPage {
}
/**
- * Return an array of subpages beginning with $search that this special page will accept.
+ * Return an array of subpages 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
+ * @return string[] subpages
*/
- public function prefixSearchSubpages( $search, $limit = 10 ) {
- $subpages = User::getAllGroups();
- return self::prefixSearchArray( $search, $limit, $subpages );
+ public function getSubpagesForPrefixSearch() {
+ return User::getAllGroups();
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index dc33801d..e44ce5f5 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -29,17 +29,6 @@
* @ingroup SpecialPage
*/
class SpecialLog extends SpecialPage {
- /**
- * List log type for which the target is a user
- * Thus if the given target is in NS_MAIN we can alter it to be an NS_USER
- * Title user instead.
- */
- private $typeOnUser = array(
- 'block',
- 'newusers',
- 'rights',
- );
-
public function __construct() {
parent::__construct( 'Log' );
}
@@ -47,6 +36,7 @@ class SpecialLog extends SpecialPage {
public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
+ $this->getOutput()->addModules( 'mediawiki.userSuggest' );
$opts = new FormOptions;
$opts->add( 'type', '' );
@@ -94,13 +84,18 @@ class SpecialLog extends SpecialPage {
} elseif ( $offender && IP::isIPAddress( $offender->getName() ) ) {
$qc = array( 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() );
}
+ } else {
+ // Allow extensions to add relations to their search types
+ Hooks::run(
+ 'SpecialLogAddLogSearchRelations',
+ array( $opts->getValue( 'type' ), $this->getRequest(), &$qc )
+ );
}
# 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 ) ) {
+ if ( in_array( $opts->getValue( 'type' ), self::getLogTypesOnUser() ) ) {
# ok we have a type of log which expect a user title.
$target = Title::newFromText( $opts->getValue( 'page' ) );
if ( $target && $target->getNamespace() === NS_MAIN ) {
@@ -115,17 +110,38 @@ class SpecialLog extends SpecialPage {
}
/**
- * Return an array of subpages beginning with $search that this special page will accept.
+ * List log type for which the target is a user
+ * Thus if the given target is in NS_MAIN we can alter it to be an NS_USER
+ * Title user instead.
*
- * @param string $search Prefix to search for
- * @param int $limit Maximum number of results to return
- * @return string[] Matching subpages
+ * @since 1.25
+ * @return array
*/
- public function prefixSearchSubpages( $search, $limit = 10 ) {
+ public static function getLogTypesOnUser() {
+ static $types = null;
+ if ( $types !== null ) {
+ return $types;
+ }
+ $types = array(
+ 'block',
+ 'newusers',
+ 'rights',
+ );
+
+ Hooks::run( 'GetLogTypesOnUser', array( &$types ) );
+ return $types;
+ }
+
+ /**
+ * Return an array of subpages that this special page will accept.
+ *
+ * @return string[] subpages
+ */
+ public function getSubpagesForPrefixSearch() {
$subpages = $this->getConfig()->get( 'LogTypes' );
$subpages[] = 'all';
sort( $subpages );
- return self::prefixSearchArray( $search, $limit, $subpages );
+ return $subpages;
}
private function parseParams( FormOptions $opts, $par ) {
@@ -149,7 +165,7 @@ class SpecialLog extends SpecialPage {
$loglist = new LogEventsList(
$this->getContext(),
null,
- LogEventsList::USE_REVDEL_CHECKBOXES
+ LogEventsList::USE_CHECKBOXES
);
$pager = new LogPager(
$loglist,
@@ -187,7 +203,7 @@ class SpecialLog extends SpecialPage {
if ( $logBody ) {
$this->getOutput()->addHTML(
$pager->getNavigationBar() .
- $this->getRevisionButton(
+ $this->getActionButtons(
$loglist->beginLogEventsList() .
$logBody .
$loglist->endLogEventsList()
@@ -199,30 +215,50 @@ class SpecialLog extends SpecialPage {
}
}
- private function getRevisionButton( $formcontents ) {
- # If the user doesn't have the ability to delete log entries,
- # don't bother showing them the button.
- if ( !$this->getUser()->isAllowedAll( 'deletedhistory', 'deletelogentry' ) ) {
+ private function getActionButtons( $formcontents ) {
+ $user = $this->getUser();
+ $canRevDelete = $user->isAllowedAll( 'deletedhistory', 'deletelogentry' );
+ $showTagEditUI = ChangeTags::showTagEditingUI( $user );
+ # If the user doesn't have the ability to delete log entries nor edit tags,
+ # don't bother showing them the button(s).
+ if ( !$canRevDelete && !$showTagEditUI ) {
return $formcontents;
}
- # Show button to hide log entries
+ # Show button to hide log entries and/or edit change tags
$s = Html::openElement(
'form',
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";
+ $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
$s .= Html::hidden( 'type', 'logging' ) . "\n";
- $button = Html::element(
- 'button',
- array(
- 'type' => 'submit',
- 'class' => "deleterevision-log-submit mw-log-deleterevision-button"
- ),
- $this->msg( 'showhideselectedlogentries' )->text()
- ) . "\n";
- $s .= $button . $formcontents . $button;
+
+ $buttons = '';
+ if ( $canRevDelete ) {
+ $buttons .= Html::element(
+ 'button',
+ array(
+ 'type' => 'submit',
+ 'name' => 'revisiondelete',
+ 'value' => '1',
+ 'class' => "deleterevision-log-submit mw-log-deleterevision-button"
+ ),
+ $this->msg( 'showhideselectedlogentries' )->text()
+ ) . "\n";
+ }
+ if ( $showTagEditUI ) {
+ $buttons .= Html::element(
+ 'button',
+ array(
+ 'type' => 'submit',
+ 'name' => 'editchangetags',
+ 'value' => '1',
+ 'class' => "editchangetags-log-submit mw-log-editchangetags-button"
+ ),
+ $this->msg( 'log-edit-tags' )->text()
+ ) . "\n";
+ }
+ $s .= $buttons . $formcontents . $buttons;
$s .= Html::closeElement( 'form' );
return $s;
@@ -235,8 +271,9 @@ class SpecialLog extends SpecialPage {
*/
protected function addHeader( $type ) {
$page = new LogPage( $type );
- $this->getOutput()->setPageTitle( $page->getName()->text() );
- $this->getOutput()->addHTML( $page->getDescription()->parseAsBlock() );
+ $this->getOutput()->setPageTitle( $page->getName() );
+ $this->getOutput()->addHTML( $page->getDescription()
+ ->setContext( $this->getContext() )->parseAsBlock() );
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
index f533234f..c072491b 100644
--- a/includes/specials/SpecialLonelypages.php
+++ b/includes/specials/SpecialLonelypages.php
@@ -72,7 +72,7 @@ class LonelyPagesPage extends PageQueryPage {
);
// Allow extensions to modify the query
- wfRunHooks( 'LonelyPagesQuery', array( &$tables, &$conds, &$joinConds ) );
+ Hooks::run( 'LonelyPagesQuery', array( &$tables, &$conds, &$joinConds ) );
return array(
'tables' => $tables,
diff --git a/includes/specials/SpecialMediaStatistics.php b/includes/specials/SpecialMediaStatistics.php
index 681c332f..b62de5d2 100644
--- a/includes/specials/SpecialMediaStatistics.php
+++ b/includes/specials/SpecialMediaStatistics.php
@@ -73,6 +73,10 @@ class MediaStatisticsPage extends QueryPage {
'namespace' => NS_MEDIA, /* needs to be something */
'value' => '1'
),
+ 'conds' => array(
+ // WMF has a random null row in the db
+ 'img_media_type IS NOT NULL'
+ ),
'options' => array(
'GROUP BY' => array(
'img_media_type',
@@ -99,7 +103,7 @@ class MediaStatisticsPage extends QueryPage {
*
* @param $out OutputPage
* @param $skin Skin (deprecated presumably)
- * @param $dbr DatabaseBase
+ * @param $dbr IDatabase
* @param $res ResultWrapper Results from query
* @param $num integer Number of results
* @param $offset integer Paging offset (Should always be 0 in our case)
@@ -153,7 +157,8 @@ class MediaStatisticsPage extends QueryPage {
);
$row .= Html::rawElement(
'td',
- array(),
+ // Make sure js sorts it in numeric order
+ array( 'data-sort-value' => $count ),
$this->msg( 'mediastatistics-nfiles' )
->numParams( $count )
/** @todo Check to be sure this really should have number formatting */
@@ -185,6 +190,9 @@ class MediaStatisticsPage extends QueryPage {
if ( $decimal == 0 ) {
return '0';
}
+ if ( $decimal >= 100 ) {
+ return '100';
+ }
$percent = sprintf( "%." . max( 0, 2 - floor( log10( $decimal ) ) ) . "f", $decimal );
// Then remove any trailing 0's
return preg_replace( '/\.?0*$/', '', $percent );
@@ -302,6 +310,8 @@ class MediaStatisticsPage extends QueryPage {
*
* @param $skin Skin
* @param $result stdObject Result row
+ * @return bool|string|void
+ * @throws MWException
*/
public function formatResult( $skin, $result ) {
throw new MWException( "unimplemented" );
@@ -310,15 +320,15 @@ class MediaStatisticsPage extends QueryPage {
/**
* Initialize total values so we can figure out percentages later.
*
- * @param $dbr DatabaseBase
+ * @param $dbr IDatabase
* @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;
+ $mediaStats = $this->splitFakeTitle( $row->title );
+ $this->totalCount += isset( $mediaStats[2] ) ? $mediaStats[2] : 0;
+ $this->totalBytes += isset( $mediaStats[3] ) ? $mediaStats[3] : 0;
}
$res->seek( 0 );
}
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index 43f5a1ba..1f0b6d45 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -159,9 +159,10 @@ class SpecialMergeHistory extends SpecialPage {
}
function showMergeForm() {
- $this->getOutput()->addWikiMsg( 'mergehistory-header' );
+ $out = $this->getOutput();
+ $out->addWikiMsg( 'mergehistory-header' );
- $this->getOutput()->addHTML(
+ $out->addHTML(
Xml::openElement( 'form', array(
'method' => 'get',
'action' => wfScript() ) ) .
@@ -185,6 +186,8 @@ class SpecialMergeHistory extends SpecialPage {
'</fieldset>' .
'</form>'
);
+
+ $this->addHelpLink( 'Help:Merge history' );
}
private function showHistory() {
@@ -469,18 +472,23 @@ class SpecialMergeHistory extends SpecialPage {
return false;
}
# Update our logs
- $log = new LogPage( 'merge' );
- $log->addEntry(
- 'merge', $targetTitle, $this->mComment,
- array( $destTitle->getPrefixedText(), $timestampLimit ), $this->getUser()
- );
+ $logEntry = new ManualLogEntry( 'merge', 'merge' );
+ $logEntry->setPerformer( $this->getUser() );
+ $logEntry->setComment( $this->mComment );
+ $logEntry->setTarget( $targetTitle );
+ $logEntry->setParameters( array(
+ '4::dest' => $destTitle->getPrefixedText(),
+ '5::mergepoint' => $timestampLimit
+ ) );
+ $logId = $logEntry->insert();
+ $logEntry->publish( $logId );
# @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 ) );
+ Hooks::run( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) );
return true;
}
@@ -516,7 +524,6 @@ class MergeHistoryPager extends ReverseChronologicalPager {
}
function getStartBody() {
- wfProfileIn( __METHOD__ );
# Do a link batch query
$this->mResult->seek( 0 );
$batch = new LinkBatch();
@@ -539,8 +546,6 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$batch->execute();
$this->mResult->seek( 0 );
- wfProfileOut( __METHOD__ );
-
return '';
}
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
index 9b67f343..c70bbdba 100644
--- a/includes/specials/SpecialMostcategories.php
+++ b/includes/specials/SpecialMostcategories.php
@@ -65,7 +65,7 @@ class MostcategoriesPage extends QueryPage {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php
index 98d8da3a..36669641 100644
--- a/includes/specials/SpecialMostimages.php
+++ b/includes/specials/SpecialMostimages.php
@@ -25,7 +25,7 @@
*/
/**
- * A special page page that list most used images
+ * A special page that lists most used images
*
* @ingroup SpecialPage
*/
diff --git a/includes/specials/SpecialMostinterwikis.php b/includes/specials/SpecialMostinterwikis.php
index 30ccbe5a..ab3d9c91 100644
--- a/includes/specials/SpecialMostinterwikis.php
+++ b/includes/specials/SpecialMostinterwikis.php
@@ -71,7 +71,7 @@ class MostinterwikisPage extends QueryPage {
/**
* Pre-fill the link cache
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index 99f0ecf5..ae0b0708 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -74,7 +74,7 @@ class MostlinkedPage extends QueryPage {
/**
* Pre-fill the link cache
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index f61a1158..cc718e06 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -55,7 +55,7 @@ class MostlinkedCategoriesPage extends QueryPage {
/**
* Fetch user page links and cache their existence
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index 8e6a596d..a924525d 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -75,7 +75,7 @@ class MostlinkedTemplatesPage extends QueryPage {
/**
* Pre-cache page existence to speed up link generation
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
public function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index ec9593f7..ae1fefea 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -140,6 +140,7 @@ class MovePageForm extends UnlistedSpecialPage {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
$out->addModules( 'mediawiki.special.movePage' );
+ $this->addHelpLink( 'Help:Moving a page' );
$newTitle = $this->newTitle;
@@ -165,17 +166,7 @@ class MovePageForm extends UnlistedSpecialPage {
$out->addWikiMsg( 'delete_and_move_text', $newTitle->getPrefixedText() );
$movepagebtn = $this->msg( 'delete_and_move' )->text();
$submitVar = 'wpDeleteAndMove';
- $confirm = "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'delete_and_move_confirm' )->text(),
- 'wpConfirm',
- 'wpConfirm'
- ) .
- "</td>
- </tr>";
+ $confirm = true;
$err = array();
} else {
if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
@@ -310,12 +301,15 @@ class MovePageForm extends UnlistedSpecialPage {
'id' => 'movepage'
)
) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, $this->msg( 'move-page-legend' )->text() ) .
- Xml::openElement( 'table', array( 'id' => 'mw-movepage-table' ) ) .
- "<tr>
+ Xml::openElement( 'fieldset' ) .
+ Xml::element( 'legend', null, $this->msg( 'move-page-legend' )->text() ) .
+ Xml::openElement( 'table', array( 'id' => 'mw-movepage-table' ) )
+ );
+
+ $out->addHTML(
+ "<tr>
<td class='mw-label'>" .
- $this->msg( 'movearticle' )->escaped() .
+ $this->msg( 'movearticle' )->escaped() .
"</td>
<td class='mw-input'>
<strong>{$oldTitleLink}</strong>
@@ -323,32 +317,32 @@ class MovePageForm extends UnlistedSpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'newtitle' )->text(), 'wpNewTitleMain' ) .
+ Xml::label( $this->msg( 'newtitle' )->text(), 'wpNewTitleMain' ) .
"</td>
<td class='mw-input'>" .
- Html::namespaceSelector(
- array(
- 'selected' => $newTitle->getNamespace(),
- 'exclude' => $immovableNamespaces
- ),
- array( 'name' => 'wpNewTitleNs', 'id' => 'wpNewTitleNs' )
- ) .
- Xml::input(
- 'wpNewTitleMain',
- 60,
- $wgContLang->recodeForEdit( $newTitle->getText() ),
- array(
- 'type' => 'text',
- 'id' => 'wpNewTitleMain',
- 'maxlength' => 255
- )
- ) .
- Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
+ Html::namespaceSelector(
+ array(
+ 'selected' => $newTitle->getNamespace(),
+ 'exclude' => $immovableNamespaces
+ ),
+ array( 'name' => 'wpNewTitleNs', 'id' => 'wpNewTitleNs' )
+ ) .
+ Xml::input(
+ 'wpNewTitleMain',
+ 60,
+ $wgContLang->recodeForEdit( $newTitle->getText() ),
+ array(
+ 'type' => 'text',
+ 'id' => 'wpNewTitleMain',
+ 'maxlength' => 255
+ )
+ ) .
+ Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
"</td>
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( $this->msg( 'movereason' )->text(), 'wpReason' ) .
+ Xml::label( $this->msg( 'movereason' )->text(), 'wpReason' ) .
"</td>
<td class='mw-input'>" .
Xml::input( 'wpReason', 60, $this->reason, array(
@@ -365,12 +359,12 @@ class MovePageForm extends UnlistedSpecialPage {
<tr>
<td></td>
<td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'movetalk' )->text(),
- 'wpMovetalk',
- 'wpMovetalk',
- $this->moveTalk
- ) .
+ Xml::checkLabel(
+ $this->msg( 'movetalk' )->text(),
+ 'wpMovetalk',
+ 'wpMovetalk',
+ $this->moveTalk
+ ) .
"</td>
</tr>"
);
@@ -389,14 +383,14 @@ class MovePageForm extends UnlistedSpecialPage {
$out->addHTML( "
<tr>
<td></td>
- <td class='mw-input' >" .
- Xml::checkLabel(
- $this->msg( 'move-leave-redirect' )->text(),
- 'wpLeaveRedirect',
- 'wpLeaveRedirect',
- $isChecked,
- $options
- ) .
+ <td class='mw-input'>" .
+ Xml::checkLabel(
+ $this->msg( 'move-leave-redirect' )->text(),
+ 'wpLeaveRedirect',
+ 'wpLeaveRedirect',
+ $isChecked,
+ $options
+ ) .
"</td>
</tr>"
);
@@ -406,13 +400,13 @@ class MovePageForm extends UnlistedSpecialPage {
$out->addHTML( "
<tr>
<td></td>
- <td class='mw-input' >" .
- Xml::checkLabel(
- $this->msg( 'fix-double-redirects' )->text(),
- 'wpFixRedirects',
- 'wpFixRedirects',
- $this->fixRedirects
- ) .
+ <td class='mw-input'>" .
+ Xml::checkLabel(
+ $this->msg( 'fix-double-redirects' )->text(),
+ 'wpFixRedirects',
+ 'wpFixRedirects',
+ $this->fixRedirects
+ ) .
"</td>
</tr>"
);
@@ -423,21 +417,23 @@ class MovePageForm extends UnlistedSpecialPage {
$out->addHTML( "
<tr>
<td></td>
- <td class=\"mw-input\">" .
- Xml::check(
- 'wpMovesubpages',
- # Don't check the box if we only have talk subpages to
- # move and we aren't moving the talk page.
- $this->moveSubpages && ( $this->oldTitle->hasSubpages() || $this->moveTalk ),
- array( 'id' => 'wpMovesubpages' )
- ) . '&#160;' .
- Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ),
- $this->msg(
- ( $this->oldTitle->hasSubpages()
- ? 'move-subpages'
- : 'move-talk-subpages' )
- )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
- ) .
+ <td class='mw-input'>" .
+ Xml::check(
+ 'wpMovesubpages',
+ # Don't check the box if we only have talk subpages to
+ # move and we aren't moving the talk page.
+ $this->moveSubpages && ( $this->oldTitle->hasSubpages() || $this->moveTalk ),
+ array( 'id' => 'wpMovesubpages' )
+ ) . '&#160;' .
+ Xml::tags(
+ 'label',
+ array( 'for' => 'wpMovesubpages' ),
+ $this->msg(
+ ( $this->oldTitle->hasSubpages()
+ ? 'move-subpages'
+ : 'move-talk-subpages' )
+ )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
+ ) .
"</td>
</tr>"
);
@@ -448,32 +444,50 @@ class MovePageForm extends UnlistedSpecialPage {
# Don't allow watching if user is not logged in
if ( $user->isLoggedIn() ) {
$out->addHTML( "
- <tr>
- <td></td>
- <td class='mw-input'>" .
- Xml::checkLabel(
- $this->msg( 'move-watch' )->text(),
- 'wpWatch',
- 'watch',
- $watchChecked
- ) .
- "</td>
- </tr>" );
+ <tr>
+ <td></td>
+ <td class='mw-input'>" .
+ Xml::checkLabel(
+ $this->msg( 'move-watch' )->text(),
+ 'wpWatch',
+ 'watch',
+ $watchChecked
+ ) .
+ "</td>
+ </tr>"
+ );
+ }
+
+ if ( $confirm ) {
+ $out->addHTML( "
+ <tr>
+ <td></td>
+ <td class='mw-input'>" .
+ Xml::checkLabel(
+ $this->msg( 'delete_and_move_confirm' )->text(),
+ 'wpConfirm',
+ 'wpConfirm'
+ ) .
+ "</td>
+ </tr>"
+ );
}
$out->addHTML( "
- {$confirm}
<tr>
- <td>&#160;</td>
+ <td></td>
<td class='mw-submit'>" .
- Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) .
+ Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) .
"</td>
- </tr>" .
- Xml::closeElement( 'table' ) .
- Html::hidden( 'wpEditToken', $user->getEditToken() ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) .
- "\n"
+ </tr>"
+ );
+
+ $out->addHTML(
+ Xml::closeElement( 'table' ) .
+ Html::hidden( 'wpEditToken', $user->getEditToken() ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' ) .
+ "\n"
);
$this->showLogFragment( $this->oldTitle );
@@ -523,8 +537,9 @@ class MovePageForm extends UnlistedSpecialPage {
// Delete an associated image if there is
if ( $nt->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $nt );
+ $file->load( File::READ_LATEST );
if ( $file->exists() ) {
- $file->delete( $reason, false );
+ $file->delete( $reason, false, $user );
}
}
@@ -549,10 +564,22 @@ class MovePageForm extends UnlistedSpecialPage {
}
# Do the actual move.
- $error = $ot->moveTo( $nt, true, $this->reason, $createRedirect );
- if ( $error !== true ) {
- $this->showForm( $error );
+ $mp = new MovePage( $ot, $nt );
+ $valid = $mp->isValidMove();
+ if ( !$valid->isOK() ) {
+ $this->showForm( $valid->getErrorsArray() );
+ return;
+ }
+
+ $permStatus = $mp->checkPermissions( $user, $this->reason );
+ if ( !$permStatus->isOK() ) {
+ $this->showForm( $permStatus->getErrorsArray() );
+ return;
+ }
+ $status = $mp->move( $user, $this->reason, $createRedirect );
+ if ( !$status->isOK() ) {
+ $this->showForm( $status->getErrorsArray() );
return;
}
@@ -592,7 +619,7 @@ class MovePageForm extends UnlistedSpecialPage {
$newLink )->params( $oldText, $newText )->parseAsBlock() );
$out->addWikiMsg( $msgName );
- wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) );
+ Hooks::run( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) );
# Now we move extra pages we've been asked to move: subpages and talk
# pages. First, if the old page or the new page is a talk page, we
@@ -673,7 +700,10 @@ class MovePageForm extends UnlistedSpecialPage {
$oldSubpage->getDBkey()
);
- if ( $oldSubpage->isTalkPage() ) {
+ if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
+ // Moving a subpage from a subject namespace to a talk namespace or vice-versa
+ $newNs = $nt->getNamespace();
+ } elseif ( $oldSubpage->isTalkPage() ) {
$newNs = $nt->getTalkPage()->getNamespace();
} else {
$newNs = $nt->getSubjectPage()->getNamespace();
diff --git a/includes/specials/SpecialMyLanguage.php b/includes/specials/SpecialMyLanguage.php
index 71b18930..6cea1581 100644
--- a/includes/specials/SpecialMyLanguage.php
+++ b/includes/specials/SpecialMyLanguage.php
@@ -80,6 +80,11 @@ class SpecialMyLanguage extends RedirectSpecialArticle {
return null;
}
+ if ( $base->isRedirect() ) {
+ $page = new WikiPage( $base );
+ $base = $page->getRedirectTarget();
+ }
+
$uiCode = $this->getLanguage()->getCode();
$proposed = $base->getSubpage( $uiCode );
if ( $uiCode !== $this->getConfig()->get( 'LanguageCode' ) && $proposed && $proposed->exists() ) {
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index 546c1914..00c8e050 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -30,6 +30,9 @@ class SpecialNewFiles extends IncludableSpecialPage {
$this->setHeaders();
$this->outputHeader();
+ $out = $this->getOutput();
+ $this->addHelpLink( 'Help:New images' );
+
$pager = new NewFilesPager( $this->getContext(), $par );
if ( !$this->including() ) {
@@ -39,9 +42,9 @@ class SpecialNewFiles extends IncludableSpecialPage {
$form->displayForm( '' );
}
- $this->getOutput()->addHTML( $pager->getBody() );
+ $out->addHTML( $pager->getBody() );
if ( !$this->including() ) {
- $this->getOutput()->addHTML( $pager->getNavigationBar() );
+ $out->addHTML( $pager->getNavigationBar() );
}
}
@@ -59,7 +62,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
if ( !$message->isDisabled() ) {
$this->getOutput()->addWikiText(
Html::rawElement( 'p',
- array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
+ array( 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ),
"\n" . $message->plain() . "\n"
),
/* $lineStart */ false,
@@ -141,7 +144,7 @@ class NewFilesPager extends ReverseChronologicalPager {
$mode = $this->getRequest()->getVal( 'gallerymode', null );
try {
$this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// User specified something invalid, fallback to default.
$this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
}
@@ -201,7 +204,10 @@ class NewFilesPager extends ReverseChronologicalPager {
$context = new DerivativeContext( $this->getContext() );
$context->setTitle( $this->getTitle() ); // Remove subpage
$form = new HTMLForm( $fields, $context );
+
$form->setSubmitTextMsg( 'ilsubmit' );
+ $form->setSubmitProgressive();
+
$form->setMethod( 'get' );
$form->setWrapperLegendMsg( 'newimages-legend' );
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 0b70bb7e..899c7368 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -56,7 +56,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$opts->add( 'invert', false );
$this->customFilters = array();
- wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) );
+ Hooks::run( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) );
foreach ( $this->customFilters as $key => $params ) {
$opts->add( $key, $params['default'] );
}
@@ -127,6 +127,8 @@ class SpecialNewpages extends IncludableSpecialPage {
$this->showNavigation = !$this->including(); // Maybe changed in setup
$this->setup( $par );
+ $this->addHelpLink( 'Help:New pages' );
+
if ( !$this->including() ) {
// Settings
$this->form();
@@ -198,6 +200,9 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function form() {
+ $out = $this->getOutput();
+ $out->addModules( 'mediawiki.userSuggest' );
+
// Consume values
$this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
$namespace = $this->opts->consumeValue( 'namespace' );
@@ -216,72 +221,62 @@ class SpecialNewpages extends IncludableSpecialPage {
}
$hidden = implode( "\n", $hidden );
- $tagFilter = ChangeTags::buildTagFilterSelector( $tagFilterVal );
- if ( $tagFilter ) {
- list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter;
- }
+ $form = array(
+ 'namespace' => array(
+ 'type' => 'namespaceselect',
+ 'name' => 'namespace',
+ 'label-message' => 'namespace',
+ 'default' => $namespace,
+ ),
+ 'nsinvert' => array(
+ 'type' => 'check',
+ 'name' => 'invert',
+ 'label-message' => 'invert',
+ 'default' => $nsinvert,
+ 'tooltip' => $this->msg( 'tooltip-invert' )->text(),
+ ),
+ 'tagFilter' => array(
+ 'type' => 'tagfilter',
+ 'name' => 'tagfilter',
+ 'label-raw' => $this->msg( 'tag-filter' )->parse(),
+ 'default' => $tagFilterVal,
+ ),
+ 'username' => array(
+ 'type' => 'text',
+ 'name' => 'username',
+ 'label-message' => 'newpages-username',
+ 'default' => $userText,
+ 'id' => 'mw-np-username',
+ 'size' => 30,
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
+ ),
+ );
+
+ $htmlForm = new HTMLForm( $form, $this->getContext() );
- $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>
- <td class="mw-label">' .
- Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
- '</td>
- <td class="mw-input">' .
- Html::namespaceSelector(
- array(
- 'selected' => $namespace,
- 'all' => 'all',
- ), array(
- 'name' => 'namespace',
- 'id' => 'namespace',
- 'class' => 'namespaceselector',
- )
- ) . '&#160;' .
- Xml::checkLabel(
- $this->msg( 'invert' )->text(),
- 'invert',
- 'nsinvert',
- $nsinvert,
- array( 'title' => $this->msg( 'tooltip-invert' )->text() )
+ $htmlForm->setSubmitText( $this->msg( 'allpagessubmit' )->text() );
+ $htmlForm->setSubmitProgressive();
+ // The form should be visible on each request (inclusive requests with submitted forms), so
+ // return always false here.
+ $htmlForm->setSubmitCallback(
+ function () {
+ return false;
+ }
+ );
+ $htmlForm->setMethod( 'get' );
+
+ $out->addHtml( Xml::fieldset( $this->msg( 'newpages' )->text() ) );
+
+ $htmlForm->show();
+
+ $out->addHtml(
+ Html::rawElement(
+ 'div',
+ null,
+ $this->filterLinks()
) .
- '</td>
- </tr>' . ( $tagFilter ? (
- '<tr>
- <td class="mw-label">' .
- $tagFilterLabel .
- '</td>
- <td class="mw-input">' .
- $tagFilterSelector .
- '</td>
- </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> <td></td>
- <td class="mw-submit">' .
- Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
- '</td>
- </tr>' .
- '<tr>
- <td></td>
- <td class="mw-input">' .
- $this->filterLinks() .
- '</td>
- </tr>' .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' ) .
- $hidden .
- Xml::closeElement( 'form' );
-
- $this->getOutput()->addHTML( $form );
+ Xml::closeElement( 'fieldset' )
+ );
}
/**
@@ -340,12 +335,12 @@ class SpecialNewpages extends IncludableSpecialPage {
$hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ),
$this->msg( 'parentheses' )->rawParams( $histLink )->escaped() );
- $length = Html::element(
+ $length = Html::rawElement(
'span',
array( 'class' => 'mw-newpages-length' ),
- $this->msg( 'brackets' )->params( $this->msg( 'nbytes' )
- ->numParams( $result->length )->text()
- )
+ $this->msg( 'brackets' )->rawParams(
+ $this->msg( 'nbytes' )->numParams( $result->length )->escaped()
+ )->escaped()
);
$ulink = Linker::revUserTools( $rev );
@@ -555,7 +550,7 @@ class NewPagesPager extends ReverseChronologicalPager {
);
$join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) );
- wfRunHooks( 'SpecialNewpagesConditions',
+ Hooks::run( 'SpecialNewpagesConditions',
array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) );
$options = array();
diff --git a/includes/specials/SpecialPageLanguage.php b/includes/specials/SpecialPageLanguage.php
index 2acf23cd..79b2444e 100644
--- a/includes/specials/SpecialPageLanguage.php
+++ b/includes/specials/SpecialPageLanguage.php
@@ -90,10 +90,12 @@ class SpecialPageLanguage extends FormSpecialPage {
return $this->showLogFragment( $this->par );
}
+ protected function getDisplayFormat() {
+ return 'vform';
+ }
+
public function alterForm( HTMLForm $form ) {
- $form->setDisplayFormat( 'vform' );
- $form->setWrapperLegend( false );
- wfRunHooks( 'LanguageSelector', array( $this->getOutput(), 'mw-languageselector' ) );
+ Hooks::run( 'LanguageSelector', array( $this->getOutput(), 'mw-languageselector' ) );
}
/**
diff --git a/includes/specials/SpecialPagesWithProp.php b/includes/specials/SpecialPagesWithProp.php
index f5b19cc6..670a3973 100644
--- a/includes/specials/SpecialPagesWithProp.php
+++ b/includes/specials/SpecialPagesWithProp.php
@@ -83,11 +83,13 @@ class SpecialPagesWithProp extends QueryPage {
*
* @param string $search Prefix to search for
* @param int $limit Maximum number of results to return
+ * @param int $offset Number of pages to skip
* @return string[] Matching subpages
*/
- public function prefixSearchSubpages( $search, $limit = 10 ) {
- $subpages = array_keys( $this->getExistingPropNames() );
- return self::prefixSearchArray( $search, $limit, $subpages );
+ public function prefixSearchSubpages( $search, $limit, $offset ) {
+ $subpages = array_keys( $this->queryExistingProps( $limit, $offset ) );
+ // We've already limited and offsetted, set to N and 0 respectively.
+ return self::prefixSearchArray( $search, count( $subpages ), $subpages, 0 );
}
/**
@@ -154,23 +156,38 @@ class SpecialPagesWithProp extends QueryPage {
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;
+ $this->existingPropNames = $this->queryExistingProps();
}
return $this->existingPropNames;
}
+ protected function queryExistingProps( $limit = null, $offset = 0 ) {
+ $opts = array(
+ 'DISTINCT', 'ORDER BY' => 'pp_propname'
+ );
+ if ( $limit ) {
+ $opts['LIMIT'] = $limit;
+ }
+ if ( $offset ) {
+ $opts['OFFSET'] = $offset;
+ }
+
+ $res = wfGetDB( DB_SLAVE )->select(
+ 'page_props',
+ 'pp_propname',
+ '',
+ __METHOD__,
+ $opts
+ );
+
+ $propnames = array();
+ foreach ( $res as $row ) {
+ $propnames[$row->pp_propname] = $row->pp_propname;
+ }
+
+ return $propnames;
+ }
+
protected function getGroupName() {
return 'pages';
}
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
index 3061c85b..a2dc2add 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -103,16 +103,13 @@ class SpecialPasswordReset extends FormSpecialPage {
return $a;
}
+ protected function getDisplayFormat() {
+ return 'vform';
+ }
+
public function alterForm( HTMLForm $form ) {
$resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
- $form->setDisplayFormat( 'vform' );
- // Turn the old-school line around the form off.
- // XXX This wouldn't be necessary here if we could set the format of
- // the HTMLForm to 'vform' at its creation, but there's no way to do so
- // from a FormSpecialPage class.
- $form->setWrapperLegend( false );
-
$form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
$i = 0;
@@ -195,7 +192,7 @@ class SpecialPasswordReset extends FormSpecialPage {
// Check for hooks (captcha etc), and allow them to modify the users list
$error = array();
- if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) {
+ if ( !Hooks::run( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) {
return array( $error );
}
@@ -246,7 +243,7 @@ class SpecialPasswordReset extends FormSpecialPage {
return array( 'badipaddress' );
}
$caller = $this->getUser();
- wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) );
+ Hooks::run( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) );
$username = $caller->getName();
$msg = IP::isValid( $username )
? 'passwordreset-emailtext-ip'
diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php
deleted file mode 100644
index 2a80f651..00000000
--- a/includes/specials/SpecialPopularpages.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-/**
- * Implements Special:PopularPages
- *
- * This 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 list most viewed pages
- *
- * @ingroup SpecialPage
- */
-class PopularPagesPage extends QueryPage {
- function __construct( $name = 'Popularpages' ) {
- parent::__construct( $name );
- }
-
- function isExpensive() {
- # page_counter is not indexed
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
- function getQueryInfo() {
- return array(
- 'tables' => array( 'page' ),
- 'fields' => array(
- 'namespace' => 'page_namespace',
- 'title' => 'page_title',
- 'value' => 'page_counter' ),
- 'conds' => array(
- 'page_is_redirect' => 0,
- 'page_namespace' => MWNamespace::getContentNamespaces()
- )
- );
- }
-
- /**
- * @param Skin $skin
- * @param object $result Result row
- * @return string
- */
- function formatResult( $skin, $result ) {
- global $wgContLang;
-
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if ( !$title ) {
- return Html::element(
- 'span',
- array( 'class' => 'mw-invalidtitle' ),
- Linker::getInvalidTitleDescription(
- $this->getContext(),
- $result->namespace,
- $result->title )
- );
- }
-
- $link = Linker::linkKnown(
- $title,
- htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
- );
- $nv = $this->msg( 'nviews' )->numParams( $result->value )->escaped();
-
- return $this->getLanguage()->specialList( $link, $nv );
- }
-
- protected function getGroupName() {
- return 'wiki';
- }
-}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index cea00fa6..7371da74 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -55,6 +55,8 @@ class SpecialPreferences extends SpecialPage {
);
}
+ $this->addHelpLink( 'Help:Preferences' );
+
$htmlForm = Preferences::getFormObject( $this->getUser(), $this->getContext() );
$htmlForm->setSubmitCallback( array( 'Preferences', 'tryUISubmit' ) );
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 2e67e2b5..5a67d924 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -102,7 +102,10 @@ class SpecialPrefixindex extends SpecialAllPages {
*/
protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) {
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
- $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getConfig()->get( 'Script' ) ) );
+ $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() );
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index 0ba73857..00e56c1c 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -39,11 +39,6 @@ class SpecialProtectedpages extends SpecialPage {
$this->outputHeader();
$this->getOutput()->addModuleStyles( 'mediawiki.special' );
- // Purge expired entries on one in every 10 queries
- if ( !mt_rand( 0, 10 ) ) {
- Title::purgeExpiredRestrictions();
- }
-
$request = $this->getRequest();
$type = $request->getVal( $this->IdType );
$level = $request->getVal( $this->IdLevel );
@@ -353,7 +348,7 @@ class ProtectedPagesPager extends TablePager {
/**
* @param string $field
* @param string $value
- * @return string
+ * @return string HTML
* @throws MWException
*/
function formatValue( $field, $value ) {
@@ -372,7 +367,8 @@ class ProtectedPagesPager extends TablePager {
$this->msg( 'protectedpages-unknown-timestamp' )->escaped()
);
} else {
- $formatted = $this->getLanguage()->userTimeAndDate( $value, $this->getUser() );
+ $formatted = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
+ $value, $this->getUser() ) );
}
break;
@@ -402,7 +398,8 @@ class ProtectedPagesPager extends TablePager {
break;
case 'pr_expiry':
- $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */true );
+ $formatted = htmlspecialchars( $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(
@@ -454,7 +451,7 @@ class ProtectedPagesPager extends TablePager {
// 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();
+ $params[] = $this->msg( 'protect-summary-cascade' )->escaped();
}
$formatted = $this->getLanguage()->commaList( $params );
break;
@@ -493,7 +490,7 @@ class ProtectedPagesPager extends TablePager {
function getQueryInfo() {
$conds = $this->mConds;
$conds[] = 'pr_expiry > ' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
- 'OR pr_expiry IS NULL';
+ ' OR pr_expiry IS NULL';
$conds[] = 'page_id=pr_page';
$conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type );
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index a40da87d..dd9198cb 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -38,11 +38,6 @@ class SpecialProtectedtitles extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
- // Purge expired entries on one in every 10 queries
- if ( !mt_rand( 0, 10 ) ) {
- Title::purgeExpiredRestrictions();
- }
-
$request = $this->getRequest();
$type = $request->getVal( $this->IdType );
$level = $request->getVal( $this->IdLevel );
@@ -72,7 +67,6 @@ class SpecialProtectedtitles extends SpecialPage {
* @return string
*/
function formatRow( $row ) {
- wfProfileIn( __METHOD__ );
static $infinity = null;
@@ -82,7 +76,6 @@ class SpecialProtectedtitles extends SpecialPage {
$title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title );
if ( !$title ) {
- wfProfileOut( __METHOD__ );
return Html::rawElement(
'li',
@@ -119,8 +112,6 @@ class SpecialProtectedtitles extends SpecialPage {
)->escaped();
}
- wfProfileOut( __METHOD__ );
-
// @todo i18n: This should use a comma separator instead of a hard coded comma, right?
return '<li>' . $lang->specialList( $link, implode( $description_items, ', ' ) ) . "</li>\n";
}
@@ -227,7 +218,6 @@ class ProtectedTitlesPager extends AlphabeticPager {
}
function getStartBody() {
- wfProfileIn( __METHOD__ );
# Do a link batch query
$this->mResult->seek( 0 );
$lb = new LinkBatch;
@@ -237,7 +227,6 @@ class ProtectedTitlesPager extends AlphabeticPager {
}
$lb->execute();
- wfProfileOut( __METHOD__ );
return '';
}
@@ -258,7 +247,8 @@ class ProtectedTitlesPager extends AlphabeticPager {
*/
function getQueryInfo() {
$conds = $this->mConds;
- $conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
+ $conds[] = 'pt_expiry > ' . $this->mDb->addQuotes( $this->mDb->timestamp() ) .
+ ' OR pt_expiry IS NULL';
if ( $this->level ) {
$conds['pt_create_perm'] = $this->level;
}
diff --git a/includes/specials/SpecialRandomInCategory.php b/includes/specials/SpecialRandomInCategory.php
index 570ab3bf..b5c9e19a 100644
--- a/includes/specials/SpecialRandomInCategory.php
+++ b/includes/specials/SpecialRandomInCategory.php
@@ -68,6 +68,8 @@ class SpecialRandomInCategory extends FormSpecialPage {
}
protected function getFormFields() {
+ $this->addHelpLink( 'Help:RandomInCategory' );
+
$form = array(
'category' => array(
'type' => 'text',
@@ -117,7 +119,7 @@ class SpecialRandomInCategory extends FormSpecialPage {
return Status::newFatal( $msg );
} elseif ( !$this->category ) {
- return; // no data sent
+ return false; // no data sent
}
$title = $this->getRandomTitle();
@@ -179,12 +181,12 @@ class SpecialRandomInCategory extends FormSpecialPage {
* @param float $rand Random number between 0 and 1
* @param int $offset Extra offset to fudge randomness
* @param bool $up True to get the result above the random number, false for below
- *
+ * @return array Query information.
+ * @throws MWException
* @note The $up parameter is supposed to counteract what would happen if there
* 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.
*/
protected function getQueryInfo( $rand, $offset, $up ) {
$op = $up ? '>=' : '<=';
@@ -230,7 +232,7 @@ class SpecialRandomInCategory extends FormSpecialPage {
if ( !$this->minTimestamp || !$this->maxTimestamp ) {
try {
list( $this->minTimestamp, $this->maxTimestamp ) = $this->getMinAndMaxForCat( $this->category );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// Possibly no entries in category.
return false;
}
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index 6d8f59b5..73a88b9e 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -106,7 +106,7 @@ class RandomPage extends SpecialPage {
$randstr = wfRandom();
$title = null;
- if ( !wfRunHooks(
+ if ( !Hooks::run(
'SpecialRandomGetRandomTitle',
array( &$randstr, &$this->isRedir, &$this->namespaces, &$this->extra, &$title )
) ) {
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index e6d8f1c3..64b0ecae 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -95,7 +95,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
protected function getCustomFilters() {
if ( $this->customFilters === null ) {
$this->customFilters = parent::getCustomFilters();
- wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ), '1.23' );
+ Hooks::run( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ), '1.23' );
}
return $this->customFilters;
@@ -252,9 +252,11 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
return $rows;
}
- protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
+ protected function runMainQueryHook( &$tables, &$fields, &$conds,
+ &$query_options, &$join_conds, $opts
+ ) {
return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
- && wfRunHooks(
+ && Hooks::run(
'SpecialRecentChangesQuery',
array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ),
'1.23'
@@ -311,7 +313,9 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
$rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
# Check if the page has been updated since the last visit
- if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) && !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
@@ -440,11 +444,11 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
$message = $this->msg( 'recentchangestext' )->inContentLanguage();
if ( !$message->isDisabled() ) {
$this->getOutput()->addWikiText(
- Html::rawElement( 'p',
- array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
+ Html::rawElement( 'div',
+ array( 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ),
"\n" . $message->plain() . "\n"
),
- /* $lineStart */ false,
+ /* $lineStart */ true,
/* $interface */ false
);
}
@@ -475,7 +479,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
// Don't fire the hook for subclasses. (Or should we?)
if ( $this->getName() === 'Recentchanges' ) {
- wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
+ Hooks::run( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
}
return $extraOpts;
@@ -732,7 +736,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
$link = $this->makeOptionsLink( $linkMessage->text(),
array( $key => 1 - $options[$key] ), $nondefaults );
- $links[] = "<span class=\"$msg rcshowhideoption\">" . $this->msg( $msg )->rawParams( $link )->escaped() . '</span>';
+ $links[] = "<span class=\"$msg rcshowhideoption\">"
+ . $this->msg( $msg )->rawParams( $link )->escaped() . '</span>';
}
// show from this onward link
diff --git a/includes/specials/SpecialRedirect.php b/includes/specials/SpecialRedirect.php
index 2022d748..72d21ebe 100644
--- a/includes/specials/SpecialRedirect.php
+++ b/includes/specials/SpecialRedirect.php
@@ -2,7 +2,6 @@
/**
* Implements Special:Redirect
*
- * @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
@@ -263,6 +262,20 @@ class SpecialRedirect extends FormSpecialPage {
$form->setMethod( 'get' );
}
+ /**
+ * Return an array of subpages that this special page will accept.
+ *
+ * @return string[] subpages
+ */
+ protected function getSubpagesForPrefixSearch() {
+ return array(
+ "file",
+ "page",
+ "revision",
+ "user",
+ );
+ }
+
protected function getGroupName() {
return 'redirects';
}
diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php
index 4add7421..ba2b9a5b 100644
--- a/includes/specials/SpecialResetTokens.php
+++ b/includes/specials/SpecialResetTokens.php
@@ -44,7 +44,7 @@ class SpecialResetTokens extends FormSpecialPage {
$tokens = array(
array( 'preference' => 'watchlisttoken', 'label-message' => 'resettokens-watchlist-token' ),
);
- wfRunHooks( 'SpecialResetTokensTokens', array( &$tokens ) );
+ Hooks::run( 'SpecialResetTokensTokens', array( &$tokens ) );
$hiddenPrefs = $this->getConfig()->get( 'HiddenPrefs' );
$tokens = array_filter( $tokens, function ( $tok ) use ( $hiddenPrefs ) {
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index 7eea71da..9e2ca277 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -132,18 +132,8 @@ 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'
- ) {
- // 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
- $this->targetObj = $this->getFullTitle();
- $this->typeName = 'revision';
- } else {
- $this->typeName = $request->getVal( 'type' );
- $this->targetObj = Title::newFromText( $request->getText( 'target' ) );
- }
+ $this->typeName = $request->getVal( 'type' );
+ $this->targetObj = Title::newFromText( $request->getText( 'target' ) );
# For reviewing deleted files...
$this->archiveName = $request->getVal( 'file' );
@@ -293,6 +283,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Show a deleted file version requested by the visitor.
* @todo Mostly copied from Special:Undelete. Refactor.
* @param string $archiveName
+ * @throws MWException
+ * @throws PermissionsError
*/
protected function tryShowFile( $archiveName ) {
$repo = RepoGroup::singleton()->getLocalRepo();
@@ -372,10 +364,12 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$userAllowed = true;
// Messages: revdelete-selected-text, revdelete-selected-file, logdelete-selected
- $this->getOutput()->wrapWikiMsg( "<strong>$1</strong>", array( $this->typeLabels['selected'],
+ $out = $this->getOutput();
+ $out->wrapWikiMsg( "<strong>$1</strong>", array( $this->typeLabels['selected'],
$this->getLanguage()->formatNum( count( $this->ids ) ), $this->targetObj->getPrefixedText() ) );
- $this->getOutput()->addHTML( "<ul>" );
+ $this->addHelpLink( 'Help:RevisionDelete' );
+ $out->addHTML( "<ul>" );
$numRevisions = 0;
// Live revisions...
@@ -393,14 +387,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
$numRevisions++;
- $this->getOutput()->addHTML( $item->getHTML() );
+ $out->addHTML( $item->getHTML() );
}
if ( !$numRevisions ) {
throw new ErrorPageError( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
}
- $this->getOutput()->addHTML( "</ul>" );
+ $out->addHTML( "</ul>" );
// Explanation text
$this->addUsageText();
@@ -411,7 +405,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// Show form if the user can submit
if ( $this->mIsAllowed ) {
- $out = Xml::openElement( 'form', array( 'method' => 'post',
+ $form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ),
'id' => 'mw-revdel-form-revisions' ) ) .
Xml::fieldset( $this->msg( 'revdelete-legend' )->text() ) .
@@ -463,12 +457,12 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
array(),
array( 'action' => 'edit' )
);
- $out .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n";
+ $form .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n";
}
} else {
- $out = '';
+ $form = '';
}
- $this->getOutput()->addHTML( $out );
+ $out->addHTML( $form );
}
/**
diff --git a/includes/specials/SpecialRunJobs.php b/includes/specials/SpecialRunJobs.php
index d4a06eb5..8cf93670 100644
--- a/includes/specials/SpecialRunJobs.php
+++ b/includes/specials/SpecialRunJobs.php
@@ -22,6 +22,8 @@
* @author Aaron Schulz
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* Special page designed for running background tasks (internal use only)
*
@@ -61,10 +63,11 @@ class SpecialRunJobs extends UnlistedSpecialPage {
$squery = $params;
unset( $squery['signature'] );
- $cSig = self::getQuerySignature( $squery, $this->getConfig()->get( 'SecretKey' ) ); // correct signature
- $rSig = $params['signature']; // provided signature
+ $correctSignature = self::getQuerySignature( $squery, $this->getConfig()->get( 'SecretKey' ) );
+ $providedSignature = $params['signature'];
- $verified = is_string( $rSig ) && hash_equals( $cSig, $rSig );
+ $verified = is_string( $providedSignature )
+ && hash_equals( $correctSignature, $providedSignature );
if ( !$verified || $params['sigexpiry'] < time() ) {
header( "HTTP/1.0 400 Bad Request" );
print 'Invalid or stale signature provided';
@@ -88,7 +91,7 @@ class SpecialRunJobs extends UnlistedSpecialPage {
// Do all of the specified tasks...
if ( in_array( 'jobs', explode( '|', $params['tasks'] ) ) ) {
- $runner = new JobRunner();
+ $runner = new JobRunner( LoggerFactory::getInstance( 'runJobs' ) );
$response = $runner->run( array(
'type' => $params['type'],
'maxJobs' => $params['maxjobs'] ? $params['maxjobs'] : 1,
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index 88ab7d82..608d62e6 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -63,7 +63,7 @@ class SpecialSearch extends SpecialPage {
/**
* @var string
*/
- protected $didYouMeanHtml, $fulltext;
+ protected $fulltext;
const NAMESPACES_CURRENT = 'sense';
@@ -165,7 +165,6 @@ class SpecialSearch extends SpecialPage {
}
}
- $this->didYouMeanHtml = ''; # html of did you mean... link
$this->fulltext = $request->getVal( 'fulltext' );
$this->profile = $profile;
}
@@ -196,7 +195,7 @@ class SpecialSearch extends SpecialPage {
# No match, generate an edit URL
$title = Title::newFromText( $term );
if ( !is_null( $title ) ) {
- wfRunHooks( 'SpecialSearchNogomatch', array( &$title ) );
+ Hooks::run( 'SpecialSearchNogomatch', array( &$title ) );
}
$this->showResults( $term );
}
@@ -207,14 +206,14 @@ class SpecialSearch extends SpecialPage {
public function showResults( $term ) {
global $wgContLang;
- $profile = new ProfileSection( __METHOD__ );
$search = $this->getSearchEngine();
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
$search->prefix = $this->mPrefix;
$term = $search->transformSearchTerm( $term );
+ $didYouMeanHtml = '';
- wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) );
+ Hooks::run( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) );
$this->setupPage( $term );
@@ -289,11 +288,14 @@ class SpecialSearch extends SpecialPage {
$stParams
);
- $this->didYouMeanHtml = '<div class="searchdidyoumean">'
- . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>';
+ # html of did you mean... search suggestion link
+ $didYouMeanHtml =
+ Xml::openElement( 'div', array( 'class' => 'searchdidyoumean' ) ) .
+ $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() .
+ Xml::closeElement( 'div' );
}
- if ( !wfRunHooks( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) {
+ if ( !Hooks::run( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) {
# Hook requested termination
return;
}
@@ -303,7 +305,7 @@ class SpecialSearch extends SpecialPage {
Xml::openElement(
'form',
array(
- 'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ),
+ 'id' => ( $this->isPowerSearch() ? 'powersearch' : 'search' ),
'method' => 'get',
'action' => wfScript(),
)
@@ -330,8 +332,10 @@ class SpecialSearch extends SpecialPage {
Xml::openElement( 'div', array( 'id' => 'mw-search-top-table' ) ) .
$this->shortDialog( $term, $num, $totalRes ) .
Xml::closeElement( 'div' ) .
- $this->formHeader( $term ) .
- Xml::closeElement( 'form' )
+ $this->searchProfileTabs( $term ) .
+ $this->searchOptions( $term ) .
+ Xml::closeElement( 'form' ) .
+ $didYouMeanHtml
);
$filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':';
@@ -360,7 +364,7 @@ class SpecialSearch extends SpecialPage {
);
}
}
- wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
+ Hooks::run( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
$out->parserOptions()->setEditSection( false );
if ( $titleMatches ) {
@@ -426,21 +430,24 @@ class SpecialSearch extends SpecialPage {
return;
}
+ $messageName = 'searchmenu-new-nocreate';
$linkClass = 'mw-search-createlink';
- if ( $title->isKnown() ) {
- $messageName = 'searchmenu-exists';
- $linkClass = 'mw-search-exists';
- } elseif ( $title->quickUserCan( 'create', $this->getUser() ) ) {
- $messageName = 'searchmenu-new';
- } else {
- $messageName = 'searchmenu-new-nocreate';
+
+ if ( !$title->isExternal() ) {
+ if ( $title->isKnown() ) {
+ $messageName = 'searchmenu-exists';
+ $linkClass = 'mw-search-exists';
+ } elseif ( $title->quickUserCan( 'create', $this->getUser() ) ) {
+ $messageName = 'searchmenu-new';
+ }
}
+
$params = array(
$messageName,
wfEscapeWikiText( $title->getPrefixedText() ),
Message::numParam( $num )
);
- wfRunHooks( 'SpecialSearchCreateLink', array( $title, &$params ) );
+ Hooks::run( 'SpecialSearchCreateLink', array( $title, &$params ) );
// Extensions using the hook might still return an empty $messageName
if ( $messageName ) {
@@ -455,8 +462,6 @@ class SpecialSearch extends SpecialPage {
* @param string $term
*/
protected function setupPage( $term ) {
- # Should advanced UI be used?
- $this->searchAdvanced = ( $this->profile === 'advanced' );
$out = $this->getOutput();
if ( strval( $term ) !== '' ) {
$out->setPageTitle( $this->msg( 'searchresults' ) );
@@ -470,6 +475,15 @@ class SpecialSearch extends SpecialPage {
}
/**
+ * Return true if current search is a power (advanced) search
+ *
+ * @return bool
+ */
+ protected function isPowerSearch() {
+ return $this->profile === 'advanced';
+ }
+
+ /**
* Extract "power search" namespace settings from the request object,
* returning a list of index numbers to search.
*
@@ -494,7 +508,7 @@ class SpecialSearch extends SpecialPage {
*/
protected function powerSearchOptions() {
$opt = array();
- if ( $this->profile !== 'advanced' ) {
+ if ( !$this->isPowerSearch() ) {
$opt['profile'] = $this->profile;
} else {
foreach ( $this->namespaces as $n ) {
@@ -519,7 +533,7 @@ class SpecialSearch extends SpecialPage {
$request->getVal( 'nsRemember' ),
'searchnamespace',
$request
- )
+ ) && !wfReadOnly()
) {
// Reset namespace preferences: namespaces are not searched
// when they're not mentioned in the URL parameters.
@@ -549,7 +563,6 @@ class SpecialSearch extends SpecialPage {
protected function showMatches( &$matches ) {
global $wgContLang;
- $profile = new ProfileSection( __METHOD__ );
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
$out = "<ul class='mw-search-results'>\n";
@@ -575,7 +588,6 @@ class SpecialSearch extends SpecialPage {
* @return string
*/
protected function showHit( $result, $terms ) {
- $profile = new ProfileSection( __METHOD__ );
if ( $result->isBrokenTitle() ) {
return '';
@@ -583,7 +595,7 @@ class SpecialSearch extends SpecialPage {
$title = $result->getTitle();
- $titleSnippet = $result->getTitleSnippet( $terms );
+ $titleSnippet = $result->getTitleSnippet();
if ( $titleSnippet == '' ) {
$titleSnippet = null;
@@ -591,7 +603,7 @@ class SpecialSearch extends SpecialPage {
$link_t = clone $title;
- wfRunHooks( 'ShowSearchHitTitle',
+ Hooks::run( 'ShowSearchHitTitle',
array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
$link = Linker::linkKnown(
@@ -615,11 +627,12 @@ class SpecialSearch extends SpecialPage {
// format redirects / relevant sections
$redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet( $terms );
+ $redirectText = $result->getRedirectSnippet();
$sectionTitle = $result->getSectionTitle();
- $sectionText = $result->getSectionSnippet( $terms );
- $redirect = '';
+ $sectionText = $result->getSectionSnippet();
+ $categorySnippet = $result->getCategorySnippet();
+ $redirect = '';
if ( !is_null( $redirectTitle ) ) {
if ( $redirectText == '' ) {
$redirectText = null;
@@ -632,7 +645,6 @@ class SpecialSearch extends SpecialPage {
}
$section = '';
-
if ( !is_null( $sectionTitle ) ) {
if ( $sectionText == '' ) {
$sectionText = null;
@@ -644,6 +656,13 @@ class SpecialSearch extends SpecialPage {
"</span>";
}
+ $category = '';
+ if ( $categorySnippet ) {
+ $category = "<span class='searchalttitle'>" .
+ $this->msg( 'search-category' )->rawParams( $categorySnippet )->text() .
+ "</span>";
+ }
+
// format text extract
$extract = "<div class='searchresult'>" . $result->getTextSnippet( $terms ) . "</div>";
@@ -688,7 +707,7 @@ class SpecialSearch extends SpecialPage {
$thumb->toHtml( array( 'desc-link' => true ) ) .
'</td>' .
'<td style="vertical-align: top;">' .
- "{$link} {$redirect} {$section} {$fileMatch}" .
+ "{$link} {$redirect} {$category} {$section} {$fileMatch}" .
$extract .
"<div class='mw-search-result-data'>{$desc} - {$date}</div>" .
'</td>' .
@@ -702,14 +721,14 @@ class SpecialSearch extends SpecialPage {
$html = null;
$score = '';
- if ( wfRunHooks( 'ShowSearchHit', array(
+ if ( Hooks::run( '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} {$fileMatch}</div> {$extract}\n" .
+ "{$link} {$redirect} {$category} {$section} {$fileMatch}</div> {$extract}\n" .
"<div class='mw-search-result-data'>{$size} - {$date}</div>" .
"</li>\n";
}
@@ -727,7 +746,6 @@ class SpecialSearch extends SpecialPage {
*/
protected function showInterwiki( $matches, $query ) {
global $wgContLang;
- $profile = new ProfileSection( __METHOD__ );
$out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>" .
$this->msg( 'search-interwiki-caption' )->text() . "</div>\n";
@@ -778,7 +796,6 @@ class SpecialSearch extends SpecialPage {
* @return string
*/
protected function showInterwikiHit( $result, $lastInterwiki, $query, $customCaptions ) {
- $profile = new ProfileSection( __METHOD__ );
if ( $result->isBrokenTitle() ) {
return '';
@@ -885,10 +902,7 @@ class SpecialSearch extends SpecialPage {
// be arranged nicely while still accommodating different screen widths
$namespaceTables = '';
for ( $i = 0; $i < $numRows; $i += 4 ) {
- $namespaceTables .= Xml::openElement(
- 'table',
- array( 'cellpadding' => 0, 'cellspacing' => 0 )
- );
+ $namespaceTables .= Xml::openElement( 'table' );
for ( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) {
$namespaceTables .= Xml::tags( 'tr', null, $rows[$j] );
@@ -899,7 +913,7 @@ class SpecialSearch extends SpecialPage {
$showSections = array( 'namespaceTables' => $namespaceTables );
- wfRunHooks( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) );
+ Hooks::run( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) );
$hidden = '';
foreach ( $opts as $key => $value ) {
@@ -911,7 +925,7 @@ class SpecialSearch extends SpecialPage {
$user = $this->getUser();
if ( $user->isLoggedIn() ) {
$remember .= Xml::checkLabel(
- wfMessage( 'powersearch-remember' )->text(),
+ $this->msg( 'powersearch-remember' )->text(),
'nsRemember',
'mw-search-powersearch-remember',
false,
@@ -970,7 +984,7 @@ class SpecialSearch extends SpecialPage {
)
);
- wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
+ Hooks::run( 'SpecialSearchProfiles', array( &$profiles ) );
foreach ( $profiles as &$data ) {
if ( !is_array( $data['namespaces'] ) ) {
@@ -986,8 +1000,8 @@ class SpecialSearch extends SpecialPage {
* @param string $term
* @return string
*/
- protected function formHeader( $term ) {
- $out = Xml::openElement( 'div', array( 'class' => 'mw-search-formheader' ) );
+ protected function searchProfileTabs( $term ) {
+ $out = Xml::openElement( 'div', array( 'class' => 'mw-search-profile-tabs' ) );
$bareterm = $term;
if ( $this->startsWithImage( $term ) ) {
@@ -1028,15 +1042,23 @@ class SpecialSearch extends SpecialPage {
$out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
$out .= Xml::closeElement( 'div' );
- // Hidden stuff
+ return $out;
+ }
+
+ /**
+ * @param string $term Search term
+ * @return string
+ */
+ protected function searchOptions( $term ) {
+ $out = '';
$opts = array();
$opts['profile'] = $this->profile;
- if ( $this->profile === 'advanced' ) {
+ if ( $this->isPowerSearch() ) {
$out .= $this->powerSearchBox( $term, $opts );
} else {
$form = '';
- wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $this->profile, $term, $opts ) );
+ Hooks::run( 'SpecialSearchProfileForm', array( $this, &$form, $this->profile, $term, $opts ) );
$out .= $form;
}
@@ -1054,15 +1076,16 @@ class SpecialSearch extends SpecialPage {
$out .= Html::hidden( 'profile', $this->profile ) . "\n";
// Term box
$out .= Html::input( 'search', $term, 'search', array(
- 'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText',
+ 'id' => $this->isPowerSearch() ? 'powerSearchText' : 'searchText',
'size' => '50',
- 'autofocus',
+ 'autofocus' => trim( $term ) === '',
'class' => 'mw-ui-input mw-ui-input-inline',
) ) . "\n";
$out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
- $out .= Xml::submitButton(
+ $out .= Html::submitButton(
$this->msg( 'searchbutton' )->text(),
- array( 'class' => array( 'mw-ui-button', 'mw-ui-progressive' ) )
+ array( 'class' => 'mw-ui-button mw-ui-progressive' ),
+ array( 'mw-ui-progressive' )
) . "\n";
// Results-info
@@ -1075,7 +1098,7 @@ class SpecialSearch extends SpecialPage {
Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
}
- return $out . $this->didYouMeanHtml;
+ return $out;
}
/**
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 782d9a17..7ec69e06 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -58,7 +58,7 @@ class ShortPagesPage extends QueryPage {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $res
*/
function preprocessResults( $db, $res ) {
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index eff06f46..eaa90072 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -45,6 +45,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
return;
}
+ $this->addHelpLink( 'Help:Special pages' );
$this->outputPageList( $groups );
}
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index f0e360e8..c35de241 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -28,7 +28,7 @@
* @ingroup SpecialPage
*/
class SpecialStatistics extends SpecialPage {
- private $views, $edits, $good, $images, $total, $users,
+ private $edits, $good, $images, $total, $users,
$activeUsers = 0;
public function __construct() {
@@ -38,13 +38,11 @@ class SpecialStatistics extends SpecialPage {
public function execute( $par ) {
global $wgMemc;
- $disableCounters = $this->getConfig()->get( 'DisableCounters' );
$miserMode = $this->getConfig()->get( 'MiserMode' );
$this->setHeaders();
$this->getOutput()->addModuleStyles( 'mediawiki.special' );
- $this->views = SiteStats::views();
$this->edits = SiteStats::edits();
$this->good = SiteStats::articles();
$this->images = SiteStats::images();
@@ -53,12 +51,6 @@ class SpecialStatistics extends SpecialPage {
$this->activeUsers = SiteStats::activeUsers();
$this->hook = '';
- # Staticic - views
- $viewsStats = '';
- if ( !$disableCounters ) {
- $viewsStats = $this->getViewsStats();
- }
-
# Set active user count
if ( !$miserMode ) {
$key = wfMemcKey( 'sitestats', 'activeusers-updated' );
@@ -83,16 +75,10 @@ class SpecialStatistics extends SpecialPage {
# Statistic - usergroups
$text .= $this->getGroupStats();
- $text .= $viewsStats;
-
- # Statistic - popular pages
- if ( !$disableCounters && !$miserMode ) {
- $text .= $this->getMostViewedPages();
- }
# Statistic - other
$extraStats = array();
- if ( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) {
+ if ( Hooks::run( 'SpecialStatsAddExtra', array( &$extraStats, $this->getContext() ) ) ) {
$text .= $this->getOtherStats( $extraStats );
}
@@ -213,10 +199,16 @@ class SpecialStatistics extends SpecialPage {
$grouppageLocalized = $msg->text();
}
$linkTarget = Title::newFromText( $grouppageLocalized );
- $grouppage = Linker::link(
- $linkTarget,
- htmlspecialchars( $groupnameLocalized )
- );
+
+ if ( $linkTarget ) {
+ $grouppage = Linker::link(
+ $linkTarget,
+ htmlspecialchars( $groupnameLocalized )
+ );
+ } else {
+ $grouppage = htmlspecialchars( $groupnameLocalized );
+ }
+
$grouplink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Listusers' ),
$this->msg( 'listgrouprights-members' )->escaped(),
@@ -237,63 +229,6 @@ class SpecialStatistics extends SpecialPage {
return $text;
}
- private function getViewsStats() {
- return Xml::openElement( 'tr' ) .
- Xml::tags( 'th', array( 'colspan' => '2' ), $this->msg( 'statistics-header-views' )->parse() ) .
- Xml::closeElement( 'tr' ) .
- $this->formatRow( $this->msg( 'statistics-views-total' )->parse(),
- $this->getLanguage()->formatNum( $this->views ),
- array( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) .
- $this->formatRow( $this->msg( 'statistics-views-peredit' )->parse(),
- $this->getLanguage()->formatNum( sprintf( '%.2f', $this->edits ?
- $this->views / $this->edits : 0 ) ),
- array( 'class' => 'mw-statistics-views-peredit' ) );
- }
-
- private function getMostViewedPages() {
- $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,
- )
- );
-
- 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 ) );
- }
- }
- $res->free();
- }
-
- return $text;
- }
-
/**
* Conversion of external statistics into an internal representation
* Following a ([<header-message>][<item-message>] = number) pattern
@@ -315,12 +250,17 @@ class SpecialStatistics extends SpecialPage {
// Collect all items that belong to the same header
foreach ( $items as $key => $value ) {
- $name = $this->msg( $key )->parse();
- $number = htmlspecialchars( $value );
+ if ( is_array( $value ) ) {
+ $name = $value['name'];
+ $number = $value['number'];
+ } else {
+ $name = $this->msg( $key )->parse();
+ $number = $value;
+ }
$return .= $this->formatRow(
$name,
- $this->getLanguage()->formatNum( $number ),
+ $this->getLanguage()->formatNum( htmlspecialchars( $number ) ),
array( 'class' => 'mw-statistics-hook', 'id' => 'mw-' . $key )
);
}
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index b7627285..0b8147e1 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -31,6 +31,10 @@ class SpecialTags extends SpecialPage {
* @var array List of defined tags
*/
public $definedTags;
+ /**
+ * @var array List of active tags
+ */
+ public $activeTags;
function __construct() {
parent::__construct( 'Tags' );
@@ -40,33 +44,114 @@ class SpecialTags extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
+ $request = $this->getRequest();
+ switch ( $par ) {
+ case 'delete':
+ $this->showDeleteTagForm( $request->getVal( 'tag' ) );
+ break;
+ case 'activate':
+ $this->showActivateDeactivateForm( $request->getVal( 'tag' ), true );
+ break;
+ case 'deactivate':
+ $this->showActivateDeactivateForm( $request->getVal( 'tag' ), false );
+ break;
+ case 'create':
+ // fall through, thanks to HTMLForm's logic
+ default:
+ $this->showTagList();
+ break;
+ }
+ }
+
+ function showTagList() {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'tags-title' ) );
$out->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' );
+ $user = $this->getUser();
+
+ // Show form to create a tag
+ if ( $user->isAllowed( 'managechangetags' ) ) {
+ $fields = array(
+ 'Tag' => array(
+ 'type' => 'text',
+ 'label' => $this->msg( 'tags-create-tag-name' )->plain(),
+ 'required' => true,
+ ),
+ 'Reason' => array(
+ 'type' => 'text',
+ 'label' => $this->msg( 'tags-create-reason' )->plain(),
+ 'size' => 50,
+ ),
+ 'IgnoreWarnings' => array(
+ 'type' => 'hidden',
+ ),
+ );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setAction( $this->getPageTitle( 'create' )->getLocalURL() );
+ $form->setWrapperLegendMsg( 'tags-create-heading' );
+ $form->setHeaderText( $this->msg( 'tags-create-explanation' )->plain() );
+ $form->setSubmitCallback( array( $this, 'processCreateTagForm' ) );
+ $form->setSubmitTextMsg( 'tags-create-submit' );
+ $form->show();
+
+ // If processCreateTagForm generated a redirect, there's no point
+ // continuing with this, as the user is just going to end up getting sent
+ // somewhere else. Additionally, if we keep going here, we end up
+ // populating the memcache of tag data (see ChangeTags::listDefinedTags)
+ // with out-of-date data from the slave, because the slave hasn't caught
+ // up to the fact that a new tag has been created as part of an implicit,
+ // as yet uncommitted transaction on master.
+ if ( $out->getRedirect() !== '' ) {
+ return;
+ }
+ }
+
+ // Whether to show the "Actions" column in the tag list
+ // If any actions added in the future require other user rights, add those
+ // rights here
+ $showActions = $user->isAllowed( 'managechangetags' );
+
// Write the headers
+ $tagUsageStatistics = ChangeTags::tagUsageStatistics();
+
+ // Show header only if there exists atleast one tag
+ if ( !$tagUsageStatistics ) {
+ return;
+ }
$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-source-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-hitcount-header' )->parse() ) .
+ ( $showActions ?
+ Xml::tags( 'th', array( 'class' => 'unsortable' ),
+ $this->msg( 'tags-actions-header' )->parse() ) :
+ '' )
);
// Used in #doTagRow()
- $this->definedTags = array_fill_keys( ChangeTags::listDefinedTags(), true );
+ $this->explicitlyDefinedTags = array_fill_keys(
+ ChangeTags::listExplicitlyDefinedTags(), true );
+ $this->extensionDefinedTags = array_fill_keys(
+ ChangeTags::listExtensionDefinedTags(), true );
+ $this->extensionActivatedTags = array_fill_keys(
+ ChangeTags::listExtensionActivatedTags(), true );
- foreach ( ChangeTags::tagUsageStatistics() as $tag => $hitcount ) {
- $html .= $this->doTagRow( $tag, $hitcount );
+ foreach ( $tagUsageStatistics as $tag => $hitcount ) {
+ $html .= $this->doTagRow( $tag, $hitcount, $showActions );
}
$out->addHTML( Xml::tags(
'table',
- array( 'class' => 'wikitable sortable mw-tags-table' ),
+ array( 'class' => 'mw-datatable sortable mw-tags-table' ),
$html
) );
}
- function doTagRow( $tag, $hitcount ) {
+ function doTagRow( $tag, $hitcount, $showActions ) {
$user = $this->getUser();
$newRow = '';
$newRow .= Xml::tags( 'td', null, Xml::element( 'code', null, $tag ) );
@@ -94,9 +179,23 @@ class SpecialTags extends SpecialPage {
}
$newRow .= Xml::tags( 'td', null, $desc );
- $active = isset( $this->definedTags[$tag] ) ? 'tags-active-yes' : 'tags-active-no';
- $active = $this->msg( $active )->escaped();
- $newRow .= Xml::tags( 'td', null, $active );
+ $sourceMsgs = array();
+ $isExtension = isset( $this->extensionDefinedTags[$tag] );
+ $isExplicit = isset( $this->explicitlyDefinedTags[$tag] );
+ if ( $isExtension ) {
+ $sourceMsgs[] = $this->msg( 'tags-source-extension' )->escaped();
+ }
+ if ( $isExplicit ) {
+ $sourceMsgs[] = $this->msg( 'tags-source-manual' )->escaped();
+ }
+ if ( !$sourceMsgs ) {
+ $sourceMsgs[] = $this->msg( 'tags-source-none' )->escaped();
+ }
+ $newRow .= Xml::tags( 'td', null, implode( Xml::element( 'br' ), $sourceMsgs ) );
+
+ $isActive = $isExplicit || isset( $this->extensionActivatedTags[$tag] );
+ $activeMsg = ( $isActive ? 'tags-active-yes' : 'tags-active-no' );
+ $newRow .= Xml::tags( 'td', null, $this->msg( $activeMsg )->escaped() );
$hitcountLabel = $this->msg( 'tags-hitcount' )->numParams( $hitcount )->escaped();
$hitcountLink = Linker::link(
@@ -109,9 +208,228 @@ class SpecialTags extends SpecialPage {
// add raw $hitcount for sorting, because tags-hitcount contains numbers and letters
$newRow .= Xml::tags( 'td', array( 'data-sort-value' => $hitcount ), $hitcountLink );
+ // actions
+ $actionLinks = array();
+ if ( $showActions ) {
+ // delete
+ if ( ChangeTags::canDeleteTag( $tag, $user )->isOK() ) {
+ $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'delete' ),
+ $this->msg( 'tags-delete' )->escaped(),
+ array(),
+ array( 'tag' => $tag ) );
+ }
+
+ // activate
+ if ( ChangeTags::canActivateTag( $tag, $user )->isOK() ) {
+ $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'activate' ),
+ $this->msg( 'tags-activate' )->escaped(),
+ array(),
+ array( 'tag' => $tag ) );
+ }
+
+ // deactivate
+ if ( ChangeTags::canDeactivateTag( $tag, $user )->isOK() ) {
+ $actionLinks[] = Linker::linkKnown( $this->getPageTitle( 'deactivate' ),
+ $this->msg( 'tags-deactivate' )->escaped(),
+ array(),
+ array( 'tag' => $tag ) );
+ }
+
+ $newRow .= Xml::tags( 'td', null, $this->getLanguage()->pipeList( $actionLinks ) );
+ }
+
return Xml::tags( 'tr', null, $newRow ) . "\n";
}
+ public function processCreateTagForm( array $data, HTMLForm $form ) {
+ $context = $form->getContext();
+ $out = $context->getOutput();
+
+ $tag = trim( strval( $data['Tag'] ) );
+ $ignoreWarnings = isset( $data['IgnoreWarnings'] ) && $data['IgnoreWarnings'] === '1';
+ $status = ChangeTags::createTagWithChecks( $tag, $data['Reason'],
+ $context->getUser(), $ignoreWarnings );
+
+ if ( $status->isGood() ) {
+ $out->redirect( $this->getPageTitle()->getLocalURL() );
+ return true;
+ } elseif ( $status->isOK() ) {
+ // we have some warnings, so we show a confirmation form
+ $fields = array(
+ 'Tag' => array(
+ 'type' => 'hidden',
+ 'default' => $data['Tag'],
+ ),
+ 'Reason' => array(
+ 'type' => 'hidden',
+ 'default' => $data['Reason'],
+ ),
+ 'IgnoreWarnings' => array(
+ 'type' => 'hidden',
+ 'default' => '1',
+ ),
+ );
+
+ // fool HTMLForm into thinking the form hasn't been submitted yet. Otherwise
+ // we get into an infinite loop!
+ $context->getRequest()->unsetVal( 'wpEditToken' );
+
+ $headerText = $this->msg( 'tags-create-warnings-above', $tag,
+ count( $status->getWarningsArray() ) )->parseAsBlock() .
+ $out->parse( $status->getWikitext() ) .
+ $this->msg( 'tags-create-warnings-below' )->parseAsBlock();
+
+ $subform = new HTMLForm( $fields, $this->getContext() );
+ $subform->setAction( $this->getPageTitle( 'create' )->getLocalURL() );
+ $subform->setWrapperLegendMsg( 'tags-create-heading' );
+ $subform->setHeaderText( $headerText );
+ $subform->setSubmitCallback( array( $this, 'processCreateTagForm' ) );
+ $subform->setSubmitTextMsg( 'htmlform-yes' );
+ $subform->show();
+
+ $out->addBacklinkSubtitle( $this->getPageTitle() );
+ return true;
+ } else {
+ $out->addWikiText( "<div class=\"error\">\n" . $status->getWikitext() .
+ "\n</div>" );
+ return false;
+ }
+ }
+
+ protected function showDeleteTagForm( $tag ) {
+ $user = $this->getUser();
+ if ( !$user->isAllowed( 'managechangetags' ) ) {
+ throw new PermissionsError( 'managechangetags' );
+ }
+
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'tags-delete-title' ) );
+ $out->addBacklinkSubtitle( $this->getPageTitle() );
+
+ // is the tag actually able to be deleted?
+ $canDeleteResult = ChangeTags::canDeleteTag( $tag, $user );
+ if ( !$canDeleteResult->isGood() ) {
+ $out->addWikiText( "<div class=\"error\">\n" . $canDeleteResult->getWikiText() .
+ "\n</div>" );
+ if ( !$canDeleteResult->isOK() ) {
+ return;
+ }
+ }
+
+ $preText = $this->msg( 'tags-delete-explanation-initial', $tag )->parseAsBlock();
+ $tagUsage = ChangeTags::tagUsageStatistics();
+ if ( $tagUsage[$tag] > 0 ) {
+ $preText .= $this->msg( 'tags-delete-explanation-in-use', $tag,
+ $tagUsage[$tag] )->parseAsBlock();
+ }
+ $preText .= $this->msg( 'tags-delete-explanation-warning', $tag )->parseAsBlock();
+
+ // see if the tag is in use
+ $this->extensionActivatedTags = array_fill_keys(
+ ChangeTags::listExtensionActivatedTags(), true );
+ if ( isset( $this->extensionActivatedTags[$tag] ) ) {
+ $preText .= $this->msg( 'tags-delete-explanation-active', $tag )->parseAsBlock();
+ }
+
+ $fields = array();
+ $fields['Reason'] = array(
+ 'type' => 'text',
+ 'label' => $this->msg( 'tags-delete-reason' )->plain(),
+ 'size' => 50,
+ );
+ $fields['HiddenTag'] = array(
+ 'type' => 'hidden',
+ 'name' => 'tag',
+ 'default' => $tag,
+ 'required' => true,
+ );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setAction( $this->getPageTitle( 'delete' )->getLocalURL() );
+ $form->tagAction = 'delete'; // custom property on HTMLForm object
+ $form->setSubmitCallback( array( $this, 'processTagForm' ) );
+ $form->setSubmitTextMsg( 'tags-delete-submit' );
+ $form->setSubmitDestructive(); // nasty!
+ $form->addPreText( $preText );
+ $form->show();
+ }
+
+ protected function showActivateDeactivateForm( $tag, $activate ) {
+ $actionStr = $activate ? 'activate' : 'deactivate';
+
+ $user = $this->getUser();
+ if ( !$user->isAllowed( 'managechangetags' ) ) {
+ throw new PermissionsError( 'managechangetags' );
+ }
+
+ $out = $this->getOutput();
+ // tags-activate-title, tags-deactivate-title
+ $out->setPageTitle( $this->msg( "tags-$actionStr-title" ) );
+ $out->addBacklinkSubtitle( $this->getPageTitle() );
+
+ // is it possible to do this?
+ $func = $activate ? 'canActivateTag' : 'canDeactivateTag';
+ $result = ChangeTags::$func( $tag, $user );
+ if ( !$result->isGood() ) {
+ $out->wrapWikiMsg( "<div class=\"error\">\n$1" . $result->getWikiText() .
+ "\n</div>" );
+ if ( !$result->isOK() ) {
+ return;
+ }
+ }
+
+ // tags-activate-question, tags-deactivate-question
+ $preText = $this->msg( "tags-$actionStr-question", $tag )->parseAsBlock();
+
+ $fields = array();
+ // tags-activate-reason, tags-deactivate-reason
+ $fields['Reason'] = array(
+ 'type' => 'text',
+ 'label' => $this->msg( "tags-$actionStr-reason" )->plain(),
+ 'size' => 50,
+ );
+ $fields['HiddenTag'] = array(
+ 'type' => 'hidden',
+ 'name' => 'tag',
+ 'default' => $tag,
+ 'required' => true,
+ );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setAction( $this->getPageTitle( $actionStr )->getLocalURL() );
+ $form->tagAction = $actionStr;
+ $form->setSubmitCallback( array( $this, 'processTagForm' ) );
+ // tags-activate-submit, tags-deactivate-submit
+ $form->setSubmitTextMsg( "tags-$actionStr-submit" );
+ $form->addPreText( $preText );
+ $form->show();
+ }
+
+ public function processTagForm( array $data, HTMLForm $form ) {
+ $context = $form->getContext();
+ $out = $context->getOutput();
+
+ $tag = $data['HiddenTag'];
+ $status = call_user_func( array( 'ChangeTags', "{$form->tagAction}TagWithChecks" ),
+ $tag, $data['Reason'], $context->getUser(), true );
+
+ if ( $status->isGood() ) {
+ $out->redirect( $this->getPageTitle()->getLocalURL() );
+ return true;
+ } elseif ( $status->isOK() && $form->tagAction === 'delete' ) {
+ // deletion succeeded, but hooks raised a warning
+ $out->addWikiText( $this->msg( 'tags-delete-warnings-after-delete', $tag,
+ count( $status->getWarningsArray() ) )->text() . "\n" .
+ $status->getWikitext() );
+ $out->addReturnTo( $this->getPageTitle() );
+ return true;
+ } else {
+ $out->addWikiText( "<div class=\"error\">\n" . $status->getWikitext() .
+ "\n</div>" );
+ return false;
+ }
+ }
+
protected function getGroupName() {
return 'changes';
}
diff --git a/includes/specials/SpecialTrackingCategories.php b/includes/specials/SpecialTrackingCategories.php
index 552031f1..d219c99d 100644
--- a/includes/specials/SpecialTrackingCategories.php
+++ b/includes/specials/SpecialTrackingCategories.php
@@ -36,6 +36,24 @@ class SpecialTrackingCategories extends SpecialPage {
parent::__construct( 'TrackingCategories' );
}
+ /**
+ * Tracking categories that exist in core
+ *
+ * @var array
+ */
+ private static $coreTrackingCategories = array(
+ 'index-category',
+ 'noindex-category',
+ 'duplicate-args-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',
+ );
+
function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
@@ -56,23 +74,88 @@ class SpecialTrackingCategories extends SpecialPage {
</tr></thead>"
);
- foreach ( $this->getConfig()->get( 'TrackingCategories' ) as $catMsg ) {
+ $trackingCategories = $this->prepareTrackingCategoriesData();
+
+ $batch = new LinkBatch();
+ foreach ( $trackingCategories as $catMsg => $data ) {
+ $batch->addObj( $data['msg'] );
+ foreach ( $data['cats'] as $catTitle ) {
+ $batch->addObj( $catTitle );
+ }
+ }
+ $batch->execute();
+
+ foreach ( $trackingCategories as $catMsg => $data ) {
+ $allMsgs = array();
+ $catDesc = $catMsg . '-desc';
+
+ $catMsgTitleText = Linker::link(
+ $data['msg'],
+ htmlspecialchars( $catMsg )
+ );
+
+ foreach ( $data['cats'] as $catTitle ) {
+ $catTitleText = Linker::link(
+ $catTitle,
+ htmlspecialchars( $catTitle->getText() )
+ );
+ $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' ) );
+ }
+
+ /**
+ * Read the global and extract title objects from the corresponding messages
+ * @return array Array( 'msg' => Title, 'cats' => Title[] )
+ */
+ private function prepareTrackingCategoriesData() {
+ $categories = array_merge(
+ self::$coreTrackingCategories,
+ ExtensionRegistry::getInstance()->getAttribute( 'TrackingCategories' ),
+ $this->getConfig()->get( 'TrackingCategories' ) // deprecated
+ );
+ $trackingCategories = array();
+ foreach ( $categories 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';
+ $allCats = array();
$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
@@ -88,11 +171,7 @@ class SpecialTrackingCategories extends SpecialPage {
if ( $catName !== '-' ) {
$catTitle = Title::makeTitleSafe( NS_CATEGORY, $catName );
if ( $catTitle ) {
- $catTitleText = Linker::link(
- $catTitle,
- htmlspecialchars( $catName )
- );
- $allMsgs[] = $catTitleText;
+ $allCats[] = $catTitle;
}
}
}
@@ -102,44 +181,17 @@ class SpecialTrackingCategories extends SpecialPage {
if ( $catName !== '-' ) {
$catTitle = Title::makeTitleSafe( NS_CATEGORY, $catName );
if ( $catTitle ) {
- $catTitleText = Linker::link(
- $catTitle,
- htmlspecialchars( $catName )
- );
- $allMsgs[] = $catTitleText;
+ $allCats[] = $catTitle;
}
}
}
-
- # 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' )
+ $trackingCategories[$catMsg] = array(
+ 'cats' => $allCats,
+ 'msg' => $catMsgTitle,
);
}
- $this->getOutput()->addHTML( Html::closeElement( 'table' ) );
+
+ return $trackingCategories;
}
protected function getGroupName() {
diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php
index 244b8894..f81f1c30 100644
--- a/includes/specials/SpecialUnblock.php
+++ b/includes/specials/SpecialUnblock.php
@@ -53,7 +53,7 @@ class SpecialUnblock extends SpecialPage {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'unblockip' ) );
- $out->addModules( 'mediawiki.special' );
+ $out->addModules( array( 'mediawiki.special', 'mediawiki.userSuggest' ) );
$form = new HTMLForm( $this->getFields(), $this->getContext() );
$form->setWrapperLegendMsg( 'unblockip' );
@@ -88,6 +88,7 @@ class SpecialUnblock extends SpecialPage {
'autofocus' => true,
'size' => '45',
'required' => true,
+ 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
),
'Name' => array(
'type' => 'info',
@@ -226,8 +227,12 @@ class SpecialUnblock extends SpecialPage {
}
# Make log entry
- $log = new LogPage( 'block' );
- $log->addEntry( 'unblock', $page, $data['Reason'], array(), $performer );
+ $logEntry = new ManualLogEntry( 'block', 'unblock' );
+ $logEntry->setTarget( $page );
+ $logEntry->setComment( $data['Reason'] );
+ $logEntry->setPerformer( $performer );
+ $logId = $logEntry->insert();
+ $logEntry->publish( $logId );
return true;
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index c3e871b8..f2362a18 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -94,7 +94,7 @@ class PageArchive {
}
/**
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param string|array $condition
* @return bool|ResultWrapper
*/
@@ -370,6 +370,7 @@ class PageArchive {
if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
$img = wfLocalFile( $this->title );
+ $img->load( File::READ_LATEST );
$this->fileStatus = $img->restore( $fileVersions, $unsuppress );
if ( !$this->fileStatus->isOK() ) {
return false;
@@ -421,7 +422,7 @@ class PageArchive {
$logEntry->setTarget( $this->title );
$logEntry->setComment( $reason );
- wfRunHooks( 'ArticleUndeleteLogEntry', array( $this, &$logEntry, $user ) );
+ Hooks::run( 'ArticleUndeleteLogEntry', array( $this, &$logEntry, $user ) );
$logid = $logEntry->insert();
$logEntry->publish( $logid );
@@ -550,7 +551,7 @@ class PageArchive {
'title' => $article->getTitle(), // used to derive default content model
)
);
- $user = User::newFromName( $revision->getRawUserText(), false );
+ $user = User::newFromName( $revision->getUserText( Revision::RAW ), false );
$content = $revision->getContent( Revision::RAW );
//NOTE: article ID may not be known yet. prepareSave() should not modify the database.
@@ -605,7 +606,7 @@ class PageArchive {
$revision->insertOn( $dbw );
$restored++;
- wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) );
+ Hooks::run( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) );
}
# Now that it's safely stored, take it out of the archive
$dbw->delete( 'archive',
@@ -623,7 +624,7 @@ class PageArchive {
$wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
if ( $created || $wasnew ) {
// Update site stats, link tables, etc
- $user = User::newFromName( $revision->getRawUserText(), false );
+ $user = User::newFromName( $revision->getUserText( Revision::RAW ), false );
$article->doEditUpdates(
$revision,
$user,
@@ -631,7 +632,7 @@ class PageArchive {
);
}
- wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment, $oldPageId ) );
+ Hooks::run( 'ArticleUndelete', array( &$this->title, $created, $comment, $oldPageId ) );
if ( $this->title->getNamespace() == NS_FILE ) {
$update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
@@ -790,6 +791,7 @@ class SpecialUndelete extends SpecialPage {
return;
}
+ $this->addHelpLink( 'Help:Undelete' );
if ( $this->mAllowed ) {
$out->setPageTitle( $this->msg( 'undeletepage' ) );
} else {
@@ -839,7 +841,7 @@ class SpecialUndelete extends SpecialPage {
'prefix',
20,
$this->mSearchPrefix,
- array( 'id' => 'prefix', 'autofocus' => true )
+ array( 'id' => 'prefix', 'autofocus' => '' )
) . ' ' .
Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
Xml::closeElement( 'fieldset' ) .
@@ -908,7 +910,7 @@ class SpecialUndelete extends SpecialPage {
}
$archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
- if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
+ if ( !Hooks::run( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
return;
}
$rev = $archive->getRevision( $timestamp );
@@ -992,7 +994,7 @@ class SpecialUndelete extends SpecialPage {
$out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
$time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
- if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) {
+ if ( !Hooks::run( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) {
return;
}
@@ -1215,7 +1217,7 @@ class SpecialUndelete extends SpecialPage {
);
$archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
- wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
+ Hooks::run( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
/*
$text = $archive->getLastRevisionText();
if( is_null( $text ) ) {
@@ -1312,7 +1314,7 @@ class SpecialUndelete extends SpecialPage {
'wpComment',
50,
$this->mComment,
- array( 'id' => 'wpComment', 'autofocus' => true )
+ array( 'id' => 'wpComment', 'autofocus' => '' )
) .
"</td>
</tr>
@@ -1628,7 +1630,9 @@ class SpecialUndelete extends SpecialPage {
}
function undelete() {
- if ( $this->getConfig()->get( 'UploadMaintenance' ) && $this->mTargetObj->getNamespace() == NS_FILE ) {
+ if ( $this->getConfig()->get( 'UploadMaintenance' )
+ && $this->mTargetObj->getNamespace() == NS_FILE
+ ) {
throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
}
@@ -1638,7 +1642,7 @@ class SpecialUndelete extends SpecialPage {
$out = $this->getOutput();
$archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
- wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
+ Hooks::run( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
$ok = $archive->undelete(
$this->mTargetTimestamp,
$this->mComment,
@@ -1649,7 +1653,7 @@ class SpecialUndelete extends SpecialPage {
if ( is_array( $ok ) ) {
if ( $ok[1] ) { // Undeleted file count
- wfRunHooks( 'FileUndeleteComplete', array(
+ Hooks::run( 'FileUndeleteComplete', array(
$this->mTargetObj, $this->mFileVersions,
$this->getUser(), $this->mComment ) );
}
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 55d09dd6..640562e4 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -143,6 +143,13 @@ class SpecialUpload extends SpecialPage {
/**
* Special page entry point
* @param string $par
+ * @throws ErrorPageError
+ * @throws Exception
+ * @throws FatalError
+ * @throws MWException
+ * @throws PermissionsError
+ * @throws ReadOnlyError
+ * @throws UserBlockedError
*/
public function execute( $par ) {
$this->setHeaders();
@@ -153,6 +160,8 @@ class SpecialUpload extends SpecialPage {
throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' );
}
+ $this->addHelpLink( 'Help:Managing files' );
+
# Check permissions
$user = $this->getUser();
$permissionRequired = UploadBase::isAllowed( $user );
@@ -186,7 +195,7 @@ class SpecialUpload extends SpecialPage {
$this->processUpload();
} else {
# Backwards compatibility hook
- if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
+ if ( !Hooks::run( 'UploadForm:initial', array( &$this ) ) ) {
wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
return;
@@ -414,7 +423,7 @@ class SpecialUpload extends SpecialPage {
return;
}
- if ( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
+ if ( !Hooks::run( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
// This code path is deprecated. If you want to break upload processing
// do so by hooking into the appropriate hooks in UploadBase::verifyUpload
@@ -454,7 +463,7 @@ class SpecialUpload extends SpecialPage {
// Get the page text if this is not a reupload
if ( !$this->mForReUpload ) {
$pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
- $this->mCopyrightStatus, $this->mCopyrightSource );
+ $this->mCopyrightStatus, $this->mCopyrightSource, $this->getConfig() );
} else {
$pageText = false;
}
@@ -474,7 +483,7 @@ class SpecialUpload extends SpecialPage {
// Success, redirect to description page
$this->mUploadSuccessful = true;
- wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
+ Hooks::run( 'SpecialUploadComplete', array( &$this ) );
$this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() );
}
@@ -484,28 +493,32 @@ class SpecialUpload extends SpecialPage {
* @param string $license
* @param string $copyStatus
* @param string $source
+ * @param Config $config Configuration object to load data from
* @return string
- * @todo Use Config obj instead of globals
*/
public static function getInitialPageText( $comment = '', $license = '',
- $copyStatus = '', $source = ''
+ $copyStatus = '', $source = '', Config $config = null
) {
- global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
+ if ( $config === null ) {
+ wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
+ $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
+ }
$msg = array();
+ $forceUIMsgAsContentMsg = (array)$config->get( 'ForceUIMsgAsContentMsg' );
/* These messages are transcluded into the actual text of the description page.
* Thus, forcing them as content messages makes the upload to produce an int: template
* instead of hardcoding it there in the uploader language.
*/
foreach ( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) {
- if ( in_array( $msgName, (array)$wgForceUIMsgAsContentMsg ) ) {
+ if ( in_array( $msgName, $forceUIMsgAsContentMsg ) ) {
$msg[$msgName] = "{{int:$msgName}}";
} else {
$msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text();
}
}
- if ( $wgUseCopyrightUpload ) {
+ if ( $config->get( 'UseCopyrightUpload' ) ) {
$licensetxt = '';
if ( $license != '' ) {
$licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
@@ -731,8 +744,8 @@ class SpecialUpload extends SpecialPage {
}
return '<li>' .
- wfMessage( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
- $gallery->toHtml() . "</li>\n";
+ $this->msg( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
+ $gallery->toHTML() . "</li>\n";
}
protected function getGroupName() {
@@ -746,7 +759,7 @@ class SpecialUpload extends SpecialPage {
*
* @todo What about non-BitmapHandler handled files?
*/
- static public function rotationEnabled() {
+ public static function rotationEnabled() {
$bitmapHandler = new BitmapHandler();
return $bitmapHandler->autoRotateEnabled();
}
@@ -795,7 +808,7 @@ class UploadForm extends HTMLForm {
+ $this->getDescriptionSection()
+ $this->getOptionsSection();
- wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) );
+ Hooks::run( 'UploadFormInitDescriptor', array( &$descriptor ) );
parent::__construct( $descriptor, $context, 'upload' );
# Add a link to edit MediaWik:Licenses
@@ -872,6 +885,17 @@ class UploadForm extends HTMLForm {
);
}
+ $help = $this->msg( 'upload-maxfilesize',
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
+ )->parse();
+
+ // If the user can also upload by URL, there are 2 different file size limits.
+ // This extra message helps stress which limit corresponds to what.
+ if ( $canUploadByUrl ) {
+ $help .= $this->msg( 'word-separator' )->escaped();
+ $help .= $this->msg( 'upload_source_file' )->parse();
+ }
+
$descriptor['UploadFile'] = array(
'class' => 'UploadSourceField',
'section' => 'source',
@@ -881,11 +905,7 @@ class UploadForm extends HTMLForm {
'label-message' => 'sourcefilename',
'upload-type' => 'File',
'radio' => &$radio,
- 'help' => $this->msg( 'upload-maxfilesize',
- $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
- )->parse() .
- $this->msg( 'word-separator' )->escaped() .
- $this->msg( 'upload_source_file' )->escaped(),
+ 'help' => $help,
'checked' => $selectedSourceType == 'file',
);
@@ -903,11 +923,11 @@ class UploadForm extends HTMLForm {
$this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
)->parse() .
$this->msg( 'word-separator' )->escaped() .
- $this->msg( 'upload_source_url' )->escaped(),
+ $this->msg( 'upload_source_url' )->parse(),
'checked' => $selectedSourceType == 'url',
);
}
- wfRunHooks( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) );
+ Hooks::run( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) );
$descriptor['Extensions'] = array(
'type' => 'info',
@@ -930,35 +950,31 @@ class UploadForm extends HTMLForm {
$config = $this->getConfig();
if ( $config->get( 'CheckFileExtensions' ) ) {
+ $fileExtensions = array_unique( $config->get( 'FileExtensions' ) );
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( $config->get( 'FileExtensions' ) )
- )
- )->parseAsBlock() .
+ $this->msg( 'upload-permitted' )
+ ->params( $this->getLanguage()->commaList( $fileExtensions ) )
+ ->numParams( count( $fileExtensions ) )
+ ->parseAsBlock() .
"</div>\n";
} else {
# We have to list both preferred and prohibited
+ $fileBlacklist = array_unique( $config->get( 'FileBlacklist' ) );
$extensionsList =
'<div id="mw-upload-preferred">' .
- $this->msg(
- 'upload-preferred',
- $this->getContext()->getLanguage()->commaList(
- array_unique( $config->get( 'FileExtensions' ) )
- )
- )->parseAsBlock() .
+ $this->msg( 'upload-preferred' )
+ ->params( $this->getLanguage()->commaList( $fileExtensions ) )
+ ->numParams( count( $fileExtensions ) )
+ ->parseAsBlock() .
"</div>\n" .
'<div id="mw-upload-prohibited">' .
- $this->msg(
- 'upload-prohibited',
- $this->getContext()->getLanguage()->commaList(
- array_unique( $config->get( 'FileBlacklist' ) )
- )
- )->parseAsBlock() .
+ $this->msg( 'upload-prohibited' )
+ ->params( $this->getLanguage()->commaList( $fileBlacklist ) )
+ ->numParams( count( $fileBlacklist ) )
+ ->parseAsBlock() .
"</div>\n";
}
} else {
@@ -978,10 +994,10 @@ class UploadForm extends HTMLForm {
protected function getDescriptionSection() {
$config = $this->getConfig();
if ( $this->mSessionKey ) {
- $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $this->getUser() );
try {
$file = $stash->getFile( $this->mSessionKey );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$file = null;
}
if ( $file ) {
@@ -1139,9 +1155,12 @@ class UploadForm extends HTMLForm {
// the wpDestFile textbox
$this->mDestFile === '',
'wgUploadSourceIds' => $this->mSourceIds,
+ 'wgCheckFileExtensions' => $config->get( 'CheckFileExtensions' ),
'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ),
+ 'wgFileExtensions' => array_values( array_unique( $config->get( 'FileExtensions' ) ) ),
'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
'wgMaxUploadSize' => $this->mMaxUploadSize,
+ 'wgFileCanRotate' => SpecialUpload::rotationEnabled(),
);
$out = $this->getOutput();
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index ddb435d9..12e103e7 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -48,10 +48,6 @@ class SpecialUploadStash extends UnlistedSpecialPage {
public function __construct() {
parent::__construct( 'UploadStash', 'upload' );
- try {
- $this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
- } catch ( UploadStashNotAvailableException $e ) {
- }
}
/**
@@ -62,6 +58,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* @return bool Success
*/
public function execute( $subPage ) {
+ $this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $this->getUser() );
$this->checkPermissions();
if ( $subPage === null || $subPage === '' ) {
@@ -250,9 +247,9 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// make a curl call to the scaler to create a thumbnail
$httpOptions = array(
'method' => 'GET',
- 'timeout' => 'default'
+ 'timeout' => 5 // T90599 attempt to time out cleanly
);
- $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions );
+ $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions, __METHOD__ );
$status = $req->execute();
if ( !$status->isOK() ) {
$errors = $status->getErrorsArray();
@@ -331,11 +328,12 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* This works, because there really is only one stash per logged-in user, despite appearances.
*
* @param array $formData
+ * @param HTMLForm $form
* @return Status
*/
- public static function tryClearStashedUploads( $formData ) {
+ public static function tryClearStashedUploads( $formData, $form ) {
if ( isset( $formData['Clear'] ) ) {
- $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $form->getUser() );
wfDebug( 'stash has: ' . print_r( $stash->listFiles(), true ) . "\n" );
if ( !$stash->clear() ) {
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 24b636b1..10edbcfb 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -105,9 +105,27 @@ class LoginForm extends SpecialPage {
* @param WebRequest $request
*/
public function __construct( $request = null ) {
+ global $wgUseMediaWikiUIEverywhere;
parent::__construct( 'Userlogin' );
$this->mOverrideRequest = $request;
+ // Override UseMediaWikiEverywhere to true, to force login and create form to use mw ui
+ $wgUseMediaWikiUIEverywhere = true;
+ }
+
+ /**
+ * Returns an array of all valid error messages.
+ *
+ * @return array
+ */
+ public static function getValidErrorMessages() {
+ static $messages = null;
+ if ( !$messages ) {
+ $messages = self::$validErrorMessages;
+ Hooks::run( 'LoginFormValidErrorMessages', array( &$messages ) );
+ }
+
+ return $messages;
}
/**
@@ -142,7 +160,8 @@ class LoginForm extends SpecialPage {
$this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
$this->mAction = $request->getVal( 'action' );
$this->mRemember = $request->getCheck( 'wpRemember' );
- $this->mFromHTTP = $request->getBool( 'fromhttp', false );
+ $this->mFromHTTP = $request->getBool( 'fromhttp', false )
+ || $request->getBool( 'wpFromhttp', false );
$this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
|| $request->getBool( 'wpForceHttps', false );
$this->mLanguage = $request->getText( 'uselang' );
@@ -172,13 +191,13 @@ class LoginForm extends SpecialPage {
// Only show valid error or warning messages.
if ( $entryError->exists()
- && in_array( $entryError->getKey(), self::$validErrorMessages )
+ && in_array( $entryError->getKey(), self::getValidErrorMessages() )
) {
$this->mEntryErrorType = 'error';
$this->mEntryError = $entryError->rawParams( $loginreqlink )->escaped();
} elseif ( $entryWarning->exists()
- && in_array( $entryWarning->getKey(), self::$validErrorMessages )
+ && in_array( $entryWarning->getKey(), self::getValidErrorMessages() )
) {
$this->mEntryErrorType = 'warning';
$this->mEntryError = $entryWarning->rawParams( $loginreqlink )->escaped();
@@ -333,7 +352,7 @@ class LoginForm extends SpecialPage {
$u->saveSettings();
$result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
- wfRunHooks( 'AddNewAccount', array( $u, true ) );
+ Hooks::run( 'AddNewAccount', array( $u, true ) );
$u->addNewUserLogEntry( 'byemail', $this->mReason );
$out = $this->getOutput();
@@ -408,7 +427,7 @@ class LoginForm extends SpecialPage {
// which is needed or the personal links will be
// wrong.
$this->getContext()->setUser( $u );
- wfRunHooks( 'AddNewAccount', array( $u, false ) );
+ Hooks::run( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry( 'create' );
if ( $this->hasSessionCookie() ) {
$this->successfulCreation();
@@ -420,7 +439,7 @@ class LoginForm extends SpecialPage {
$out->setPageTitle( $this->msg( 'accountcreated' ) );
$out->addWikiMsg( 'accountcreatedtext', $u->getName() );
$out->addReturnTo( $this->getPageTitle() );
- wfRunHooks( 'AddNewAccount', array( $u, false ) );
+ Hooks::run( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry( 'create2', $this->mReason );
}
@@ -509,20 +528,8 @@ 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
- $u = User::newFromName( $title->getText(), 'creatable' );
+ $u = User::newFromName( $this->mUsername, 'creatable' );
if ( !is_object( $u ) ) {
return Status::newFatal( 'noname' );
} elseif ( 0 != $u->idForName() ) {
@@ -563,7 +570,7 @@ class LoginForm extends SpecialPage {
$abortError = '';
$abortStatus = null;
- if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError, &$abortStatus ) ) ) {
+ if ( !Hooks::run( 'AbortNewAccount', array( $u, &$abortError, &$abortStatus ) ) ) {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
if ( $abortStatus === null ) {
@@ -583,7 +590,7 @@ class LoginForm extends SpecialPage {
}
// Hook point to check for exempt from account creation throttle
- if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) {
+ if ( !Hooks::run( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) {
wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook " .
"allowed account creation w/o throttle\n" );
} else {
@@ -706,7 +713,7 @@ class LoginForm extends SpecialPage {
// 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 ) ) ) {
+ if ( !Hooks::run( 'LoginUserMigrated', array( $u, &$msg ) ) ) {
$this->mAbortLoginErrorMsg = $msg;
return self::USER_MIGRATED;
}
@@ -730,7 +737,7 @@ class LoginForm extends SpecialPage {
// Give general extensions, such as a captcha, a chance to abort logins
$abort = self::ABORTED;
$msg = null;
- if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
+ if ( !Hooks::run( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
$this->mAbortLoginErrorMsg = $msg;
return $abort;
@@ -755,7 +762,7 @@ class LoginForm extends SpecialPage {
// As a side-effect, we can authenticate the user's e-mail ad-
// dress if it's not already done, since the temporary password
// was sent via e-mail.
- if ( !$u->isEmailConfirmed() ) {
+ if ( !$u->isEmailConfirmed() && !wfReadOnly() ) {
$u->confirmEmail();
$u->saveSettings();
}
@@ -791,12 +798,12 @@ class LoginForm extends SpecialPage {
if ( $isAutoCreated ) {
// Must be run after $wgUser is set, for correct new user log
- wfRunHooks( 'AuthPluginAutoCreate', array( $u ) );
+ Hooks::run( 'AuthPluginAutoCreate', array( $u ) );
}
$retval = self::SUCCESS;
}
- wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) );
+ Hooks::run( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) );
return $retval;
}
@@ -877,7 +884,7 @@ class LoginForm extends SpecialPage {
}
$abortError = '';
- if ( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
+ if ( !Hooks::run( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
// Hook point to add extra creation throttles and blocks
wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
$this->mAbortLoginErrorMsg = $abortError;
@@ -906,7 +913,7 @@ class LoginForm extends SpecialPage {
case self::SUCCESS:
# We've verified now, update the real record
$user = $this->getUser();
- $user->invalidateCache();
+ $user->touch();
if ( $user->requiresHTTPS() ) {
$this->mStickHTTPS = true;
@@ -1030,7 +1037,7 @@ class LoginForm extends SpecialPage {
*/
protected function resetLoginForm( Message $msg ) {
// Allow hooks to explain this password reset in more detail
- wfRunHooks( 'LoginPasswordResetMessage', array( &$msg, $this->mUsername ) );
+ Hooks::run( 'LoginPasswordResetMessage', array( &$msg, $this->mUsername ) );
$reset = new SpecialChangePassword();
$derivative = new DerivativeContext( $this->getContext() );
$derivative->setTitle( $reset->getPageTitle() );
@@ -1063,7 +1070,7 @@ class LoginForm extends SpecialPage {
}
$currentUser = $this->getUser();
- wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) );
+ Hooks::run( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) );
$np = $u->randomPassword();
$u->setNewpassword( $np, $throttle );
@@ -1094,7 +1101,7 @@ class LoginForm extends SpecialPage {
# Run any hooks; display injected HTML if any, else redirect
$currentUser = $this->getUser();
$injected_html = '';
- wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
+ Hooks::run( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
if ( $injected_html !== '' ) {
$this->displaySuccessfulAction( 'success', $this->msg( 'loginsuccesstitle' ),
@@ -1116,14 +1123,14 @@ class LoginForm extends SpecialPage {
$injected_html = '';
$welcome_creation_msg = 'welcomecreation-msg';
- wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
+ Hooks::run( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
/**
* Let any extensions change what message is shown.
* @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeWelcomeCreation
* @since 1.18
*/
- wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
+ Hooks::run( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
$this->displaySuccessfulAction(
'signup',
@@ -1233,7 +1240,7 @@ class LoginForm extends SpecialPage {
}
// Allow modification of redirect behavior
- wfRunHooks( 'PostLoginRedirect', array( &$returnTo, &$returnToQuery, &$type ) );
+ Hooks::run( 'PostLoginRedirect', array( &$returnTo, &$returnToQuery, &$type ) );
$returnToTitle = Title::newFromText( $returnTo );
if ( !$returnToTitle ) {
@@ -1262,6 +1269,12 @@ class LoginForm extends SpecialPage {
/**
* @param string $msg
* @param string $msgtype
+ * @throws ErrorPageError
+ * @throws Exception
+ * @throws FatalError
+ * @throws MWException
+ * @throws PermissionsError
+ * @throws ReadOnlyError
* @private
*/
function mainLoginForm( $msg, $msgtype = 'error' ) {
@@ -1325,7 +1338,7 @@ class LoginForm extends SpecialPage {
'mediawiki.special.userlogin.signup.styles'
) );
- $template = new UsercreateTemplate();
+ $template = new UsercreateTemplate( $this->getConfig() );
// Must match number of benefits defined in messages
$template->set( 'benefitCount', 3 );
@@ -1338,7 +1351,7 @@ class LoginForm extends SpecialPage {
'mediawiki.special.userlogin.login.styles'
) );
- $template = new UserloginTemplate();
+ $template = new UserloginTemplate( $this->getConfig() );
$q = 'action=submitlogin&type=login';
$linkq = 'type=signup';
@@ -1420,28 +1433,26 @@ 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.
+ // Use signupend-https for HTTPS requests if it's not blank, signupend otherwise
$usingHTTPS = $this->mRequest->getProtocol() == 'https';
- $loginendHTTPS = $this->msg( 'loginend-https' );
$signupendHTTPS = $this->msg( 'signupend-https' );
- if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
- $template->set( 'loginend', $loginendHTTPS->parse() );
- } else {
- $template->set( 'loginend', $this->msg( 'loginend' )->parse() );
- }
if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
$template->set( 'signupend', $signupendHTTPS->parse() );
} else {
$template->set( 'signupend', $this->msg( 'signupend' )->parse() );
}
+ // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
+ if ( $usingHTTPS ) {
+ $template->set( 'fromhttp', $this->mFromHTTP );
+ }
+
// Give authentication and captcha plugins a chance to modify the form
$wgAuth->modifyUITemplate( $template, $this->mType );
if ( $this->mType == 'signup' ) {
- wfRunHooks( 'UserCreateForm', array( &$template ) );
+ Hooks::run( 'UserCreateForm', array( &$template ) );
} else {
- wfRunHooks( 'UserLoginForm', array( &$template ) );
+ Hooks::run( 'UserLoginForm', array( &$template ) );
}
$out->disallowUserJs(); // just in case...
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
index d65ac852..080dc119 100644
--- a/includes/specials/SpecialUserlogout.php
+++ b/includes/specials/SpecialUserlogout.php
@@ -56,7 +56,7 @@ class SpecialUserlogout extends UnlistedSpecialPage {
// Hook.
$injected_html = '';
- wfRunHooks( 'UserLogoutComplete', array( &$user, &$injected_html, $oldName ) );
+ Hooks::run( 'UserLogoutComplete', array( &$user, &$injected_html, $oldName ) );
$out->addHTML( $injected_html );
$out->returnToMain();
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index cefdad07..758e3c05 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -106,7 +106,7 @@ class UserrightsPage extends SpecialPage {
}
}
- if ( User::getCanonicalName( $this->mTarget ) == $user->getName() ) {
+ if ( User::getCanonicalName( $this->mTarget ) === $user->getName() ) {
$this->isself = true;
}
@@ -135,6 +135,7 @@ class UserrightsPage extends SpecialPage {
$out = $this->getOutput();
$out->addModuleStyles( 'mediawiki.special' );
+ $this->addHelpLink( 'Help:Assigning permissions' );
// show the general form
if ( count( $available['add'] ) || count( $available['remove'] ) ) {
@@ -218,7 +219,7 @@ class UserrightsPage extends SpecialPage {
/**
* Save user groups changes in the database.
*
- * @param User $user
+ * @param User|UserRightsProxy $user
* @param array $add Array of groups to add
* @param array $remove Array of groups to remove
* @param string $reason Reason for group change
@@ -228,7 +229,7 @@ class UserrightsPage extends SpecialPage {
global $wgAuth;
// Validate input set...
- $isself = ( $user->getName() == $this->getUser()->getName() );
+ $isself = $user->getName() == $this->getUser()->getName();
$groups = $user->getGroups();
$changeable = $this->changeableGroups();
$addable = array_merge( $changeable['add'], $isself ? $changeable['add-self'] : array() );
@@ -244,18 +245,22 @@ class UserrightsPage extends SpecialPage {
$oldGroups = $user->getGroups();
$newGroups = $oldGroups;
- // remove then add groups
+ // Remove then add groups
if ( $remove ) {
- $newGroups = array_diff( $newGroups, $remove );
- foreach ( $remove as $group ) {
- $user->removeGroup( $group );
+ foreach ( $remove as $index => $group ) {
+ if ( !$user->removeGroup( $group ) ) {
+ unset($remove[$index]);
+ }
}
+ $newGroups = array_diff( $newGroups, $remove );
}
if ( $add ) {
- $newGroups = array_merge( $newGroups, $add );
- foreach ( $add as $group ) {
- $user->addGroup( $group );
+ foreach ( $add as $index => $group ) {
+ if ( !$user->addGroup( $group ) ) {
+ unset($add[$index]);
+ }
}
+ $newGroups = array_merge( $newGroups, $add );
}
$newGroups = array_unique( $newGroups );
@@ -267,7 +272,7 @@ class UserrightsPage extends SpecialPage {
wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) . "\n" );
wfDebug( 'newGroups: ' . print_r( $newGroups, true ) . "\n" );
- wfRunHooks( 'UserRights', array( &$user, $add, $remove ) );
+ Hooks::run( 'UserRights', array( &$user, $add, $remove ) );
if ( $newGroups != $oldGroups ) {
$this->addLogEntry( $user, $oldGroups, $newGroups, $reason );
@@ -415,6 +420,8 @@ class UserrightsPage extends SpecialPage {
* Output a form to allow searching for a user
*/
function switchForm() {
+ $this->getOutput()->addModules( 'mediawiki.userSuggest' );
+
$this->getOutput()->addHTML(
Html::openElement(
'form',
@@ -433,7 +440,10 @@ class UserrightsPage extends SpecialPage {
'username',
30,
str_replace( '_', ' ', $this->mTarget ),
- array( 'autofocus' => true )
+ array(
+ 'autofocus' => '',
+ 'class' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
+ )
) . ' ' .
Xml::submitButton( $this->msg( 'editusergroup' )->text() ) .
Html::closeElement( 'fieldset' ) .
@@ -488,25 +498,32 @@ class UserrightsPage extends SpecialPage {
}
$language = $this->getLanguage();
- $displayedList = $this->msg( 'userrights-groupsmember-type',
- $language->listToText( $list ),
- $language->listToText( $membersList )
- )->plain();
- $displayedAutolist = $this->msg( 'userrights-groupsmember-type',
- $language->listToText( $autoList ),
- $language->listToText( $autoMembersList )
- )->plain();
+ $displayedList = $this->msg( 'userrights-groupsmember-type' )
+ ->rawParams(
+ $language->listToText( $list ),
+ $language->listToText( $membersList )
+ )->escaped();
+ $displayedAutolist = $this->msg( 'userrights-groupsmember-type' )
+ ->rawParams(
+ $language->listToText( $autoList ),
+ $language->listToText( $autoMembersList )
+ )->escaped();
$grouplist = '';
$count = count( $list );
if ( $count > 0 ) {
- $grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
+ $grouplist = $this->msg( 'userrights-groupsmember' )
+ ->numParams( $count )
+ ->params( $user->getName() )
+ ->parse();
$grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
}
$count = count( $autoList );
if ( $count > 0 ) {
- $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )
+ $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto' )
+ ->numParams( $count )
+ ->params( $user->getName() )
->parse();
$grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
}
@@ -664,9 +681,9 @@ class UserrightsPage extends SpecialPage {
$member = User::getGroupMember( $group, $user->getName() );
if ( $checkbox['irreversible'] ) {
- $text = $this->msg( 'userrights-irreversible-marker', $member )->escaped();
+ $text = $this->msg( 'userrights-irreversible-marker', $member )->text();
} else {
- $text = htmlspecialchars( $member );
+ $text = $member;
}
$checkboxHtml = Xml::checkLabel( $text, "wpGroup-" . $group,
"wpGroup-" . $group, $checkbox['set'], $attr );
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index cb3fc118..c1a95939 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -109,12 +109,7 @@ class SpecialVersion extends SpecialPage {
$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>";
- }
+ $wikiText = "<pre>$wikiText</pre>";
}
}
@@ -132,6 +127,7 @@ class SpecialVersion extends SpecialPage {
$out->addHtml(
$this->getSkinCredits() .
$this->getExtensionCredits() .
+ $this->getExternalLibraries() .
$this->getParserTags() .
$this->getParserFunctionHooks()
);
@@ -191,8 +187,8 @@ class SpecialVersion extends SpecialPage {
'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
- 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', $othersLink,
- $translatorsLink
+ 'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
+ $othersLink, $translatorsLink
);
return wfMessage( 'version-poweredby-credits', MWTimestamp::getLocalInstance()->format( 'Y' ),
@@ -220,7 +216,7 @@ class SpecialVersion extends SpecialPage {
$software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
// Allow a hook to add/remove items.
- wfRunHooks( 'SoftwareInfo', array( &$software ) );
+ Hooks::run( 'SoftwareInfo', array( &$software ) );
$out = Xml::element(
'h2',
@@ -251,7 +247,6 @@ class SpecialVersion extends SpecialPage {
*/
public static function getVersion( $flags = '' ) {
global $wgVersion, $IP;
- wfProfileIn( __METHOD__ );
$gitInfo = self::getGitHeadSha1( $IP );
$svnInfo = self::getSvnInfo( $IP );
@@ -275,8 +270,6 @@ class SpecialVersion extends SpecialPage {
)->text();
}
- wfProfileOut( __METHOD__ );
-
return $version;
}
@@ -290,7 +283,6 @@ class SpecialVersion extends SpecialPage {
*/
public static function getVersionLinked() {
global $wgVersion;
- wfProfileIn( __METHOD__ );
$gitVersion = self::getVersionLinkedGit();
if ( $gitVersion ) {
@@ -304,8 +296,6 @@ class SpecialVersion extends SpecialPage {
}
}
- wfProfileOut( __METHOD__ );
-
return $v;
}
@@ -341,7 +331,7 @@ class SpecialVersion extends SpecialPage {
private static function getwgVersionLinked() {
global $wgVersion;
$versionUrl = "";
- if ( wfRunHooks( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) {
+ if ( Hooks::run( 'SpecialVersionVersionUrl', array( $wgVersion, &$versionUrl ) ) ) {
$versionParts = array();
preg_match( "/^(\d+\.\d+)/", $wgVersion, $versionParts );
$versionUrl = "https://www.mediawiki.org/wiki/MediaWiki_{$versionParts[1]}";
@@ -402,7 +392,7 @@ class SpecialVersion extends SpecialPage {
'other' => wfMessage( 'version-other' )->text(),
);
- wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) );
+ Hooks::run( 'ExtensionTypes', array( &self::$extensionTypes ) );
}
return self::$extensionTypes;
@@ -504,6 +494,57 @@ class SpecialVersion extends SpecialPage {
}
/**
+ * Generate an HTML table for external libraries that are installed
+ *
+ * @return string
+ */
+ protected function getExternalLibraries() {
+ global $IP;
+ $path = "$IP/composer.lock";
+ if ( !file_exists( $path ) ) {
+ // Maybe they're using mediawiki/vendor?
+ $path = "$IP/vendor/composer.lock";
+ if ( !file_exists( $path ) ) {
+ return '';
+ }
+ }
+
+ $lock = new ComposerLock( $path );
+ $out = Html::element(
+ 'h2',
+ array( 'id' => 'mw-version-libraries' ),
+ $this->msg( 'version-libraries' )->text()
+ );
+ $out .= Html::openElement(
+ 'table',
+ array( 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' )
+ );
+ $out .= Html::openElement( 'tr' )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-library' )->text() )
+ . Html::element( 'th', array(), $this->msg( 'version-libraries-version' )->text() )
+ . Html::closeElement( 'tr' );
+
+ foreach ( $lock->getInstalledDependencies() as $name => $info ) {
+ if ( strpos( $info['type'], 'mediawiki-' ) === 0 ) {
+ // Skip any extensions or skins since they'll be listed
+ // in their proper section
+ continue;
+ }
+ $out .= Html::openElement( 'tr' )
+ . Html::rawElement(
+ 'td',
+ array(),
+ Linker::makeExternalLink( "https://packagist.org/packages/$name", $name )
+ )
+ . Html::element( 'td', array(), $info['version'] )
+ . Html::closeElement( 'tr' );
+ }
+ $out .= Html::closeElement( 'table' );
+
+ return $out;
+ }
+
+ /**
* Obtains a list of installed parser tags and the associated H2 header
*
* @return string HTML output
@@ -516,7 +557,7 @@ class SpecialVersion extends SpecialPage {
if ( count( $tags ) ) {
$out = Html::rawElement(
'h2',
- array( 'class' => 'mw-headline' ),
+ array( 'class' => 'mw-headline plainlinks' ),
Linker::makeExternalLink(
'//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Tag_extensions',
$this->msg( 'version-parser-extensiontags' )->parse(),
@@ -525,8 +566,17 @@ class SpecialVersion extends SpecialPage {
);
array_walk( $tags, function ( &$value ) {
- $value = '&lt;' . htmlspecialchars( $value ) . '&gt;';
+ // Bidirectional isolation improves readability in RTL wikis
+ $value = Html::element(
+ 'bdi',
+ // Prevent < and > from slipping to another line
+ array(
+ 'style' => 'white-space: nowrap;',
+ ),
+ "<$value>"
+ );
} );
+
$out .= $this->listToText( $tags );
} else {
$out = '';
@@ -545,11 +595,15 @@ class SpecialVersion extends SpecialPage {
$fhooks = $wgParser->getFunctionHooks();
if ( count( $fhooks ) ) {
- $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 = Html::rawElement(
+ 'h2',
+ array( 'class' => 'mw-headline plainlinks' ),
+ Linker::makeExternalLink(
+ '//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Parser_functions',
+ $this->msg( 'version-parser-function-hooks' )->parse(),
+ false /* msg()->parse() already escapes */
+ )
+ );
$out .= $this->listToText( $fhooks );
} else {
@@ -680,7 +734,7 @@ class SpecialVersion extends SpecialPage {
list( $vcsVersion, $vcsLink, $vcsDate ) = $cache->get( $memcKey );
if ( !$vcsVersion ) {
- wfDebug( "Getting VCS info for extension $extensionName" );
+ wfDebug( "Getting VCS info for extension {$extension['name']}" );
$gitInfo = new GitInfo( $extensionPath );
$vcsVersion = $gitInfo->getHeadSHA1();
if ( $vcsVersion !== false ) {
@@ -696,7 +750,7 @@ class SpecialVersion extends SpecialPage {
}
$cache->set( $memcKey, array( $vcsVersion, $vcsLink, $vcsDate ), 60 * 60 * 24 );
} else {
- wfDebug( "Pulled VCS info for extension $extensionName from cache" );
+ wfDebug( "Pulled VCS info for extension {$extension['name']} from cache" );
}
}
@@ -739,18 +793,23 @@ class SpecialVersion extends SpecialPage {
// ... 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' )
- );
+ if ( isset( $extension['name'] ) ) {
+ $licenseName = null;
+ if ( isset( $extension['license-name'] ) ) {
+ $licenseName = $out->parseInline( $extension['license-name'] );
+ } elseif ( $this->getExtLicenseFileName( $extensionPath ) ) {
+ $licenseName = $this->msg( 'version-ext-license' );
+ }
+ if ( $licenseName !== null ) {
+ $licenseLink = Linker::link(
+ $this->getPageTitle( 'License/' . $extension['name'] ),
+ $licenseName,
+ array(
+ 'class' => 'mw-version-ext-license',
+ 'dir' => 'auto',
+ )
+ );
+ }
}
// ... and generate the description; which can be a parameterized l10n message
@@ -778,12 +837,12 @@ class SpecialVersion extends SpecialPage {
// ... now get the authors for this extension
$authors = isset( $extension['author'] ) ? $extension['author'] : array();
- $authors = $this->listAuthors( $authors, $extensionName, $extensionPath );
+ $authors = $this->listAuthors( $authors, $extension['name'], $extensionPath );
// Finally! Create the table
$html = Html::openElement( 'tr', array(
'class' => 'mw-version-ext',
- 'id' => "mw-version-ext-{$extensionName}"
+ 'id' => "mw-version-ext-{$extension['name']}"
)
);
@@ -793,7 +852,7 @@ class SpecialVersion extends SpecialPage {
$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' );
+ $html .= Html::closeElement( 'tr' );
return $html;
}
@@ -916,10 +975,10 @@ class SpecialVersion extends SpecialPage {
if ( $this->getExtAuthorsFileName( $extDir ) ) {
$text = Linker::link(
$this->getPageTitle( "Credits/$extName" ),
- $this->msg( 'version-poweredby-others' )->text()
+ $this->msg( 'version-poweredby-others' )->escaped()
);
} else {
- $text = $this->msg( 'version-poweredby-others' )->text();
+ $text = $this->msg( 'version-poweredby-others' )->escaped();
}
$list[] = $text;
} elseif ( substr( $item, -5 ) == ' ...]' ) {
@@ -935,7 +994,7 @@ class SpecialVersion extends SpecialPage {
if ( !$hasOthers && $this->getExtAuthorsFileName( $extDir ) ) {
$list[] = $text = Linker::link(
$this->getPageTitle( "Credits/$extName" ),
- $this->msg( 'version-poweredby-others' )->text()
+ $this->msg( 'version-poweredby-others' )->escaped()
);
}
@@ -1024,7 +1083,7 @@ class SpecialVersion extends SpecialPage {
* Convert an array or object to a string for display.
*
* @param mixed $list Will convert an array to string if given and return
- * the paramater unaltered otherwise
+ * the parameter unaltered otherwise
*
* @return mixed
*/
@@ -1188,7 +1247,7 @@ class SpecialVersion extends SpecialPage {
$language = $this->getLanguage();
$thAttribures = array(
'dir' => $language->getDir(),
- 'lang' => $language->getCode()
+ 'lang' => $language->getHtmlCode()
);
$out = Html::element(
'h2',
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index b8c0bb28..7ddafae4 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -109,6 +109,7 @@ class WantedCategoriesPage extends WantedQueryPage {
$currentValue = isset( $this->currentCategoryCounts[$result->title] )
? $this->currentCategoryCounts[$result->title]
: 0;
+ $cachedValue = intval( $result->value ); // T76910
// If the category has been created or emptied since the list was refreshed, strike it
if ( $nt->isKnown() || $currentValue === 0 ) {
@@ -116,11 +117,11 @@ class WantedCategoriesPage extends WantedQueryPage {
}
// Show the current number of category entries if it changed
- if ( $currentValue !== $result->value ) {
+ if ( $currentValue !== $cachedValue ) {
$nlinks = $this->msg( 'nmemberschanged' )
- ->numParams( $result->value, $currentValue )->escaped();
+ ->numParams( $cachedValue, $currentValue )->escaped();
} else {
- $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ $nlinks = $this->msg( 'nmembers' )->numParams( $cachedValue )->escaped();
}
}
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index 937a503c..8a1a6c6c 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -99,10 +99,10 @@ class WantedFilesPage extends WantedQueryPage {
* Use wfFindFile so we still think file namespace pages without
* files are missing, but valid file redirects and foreign files are ok.
*
- * @return boolean
+ * @return bool
*/
protected function existenceCheck( Title $title ) {
- return (bool) wfFindFile( $title );
+ return (bool)wfFindFile( $title );
}
function getQueryInfo() {
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index 38f1808f..dd4eb0a8 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -72,7 +72,10 @@ class WantedPagesPage extends WantedQueryPage {
"pg2.page_namespace != '" . NS_MEDIAWIKI . "'"
),
'options' => array(
- 'HAVING' => "COUNT(*) > $count",
+ 'HAVING' => array(
+ "COUNT(*) > $count",
+ "COUNT(*) > SUM(pg2.page_is_redirect)"
+ ),
'GROUP BY' => array( 'pl_namespace', 'pl_title' )
),
'join_conds' => array(
@@ -86,7 +89,7 @@ class WantedPagesPage extends WantedQueryPage {
)
);
// Replacement for the WantedPages::getSQL hook
- wfRunHooks( 'WantedPages::getQueryInfo', array( &$this, &$query ) );
+ Hooks::run( 'WantedPages::getQueryInfo', array( &$this, &$query ) );
return $query;
}
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index 8f2f86b9..df9d3639 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -79,22 +79,16 @@ class SpecialWatchlist extends ChangesListSpecialPage {
}
/**
- * Return an array of subpages beginning with $search that this special page will accept.
+ * Return an array of subpages 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
+ * @see also SpecialEditWatchlist::getSubpagesForPrefixSearch
+ * @return string[] subpages
*/
- public function prefixSearchSubpages( $search, $limit = 10 ) {
- // See also SpecialEditWatchlist::prefixSearchSubpages
- return self::prefixSearchArray(
- $search,
- $limit,
- array(
- 'clear',
- 'edit',
- 'raw',
- )
+ public function getSubpagesForPrefixSearch() {
+ return array(
+ 'clear',
+ 'edit',
+ 'raw',
);
}
@@ -129,7 +123,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
protected function getCustomFilters() {
if ( $this->customFilters === null ) {
$this->customFilters = parent::getCustomFilters();
- wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ), '1.23' );
+ Hooks::run( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ), '1.23' );
}
return $this->customFilters;
@@ -207,7 +201,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
} else {
# Top log Ids for a page are not stored
$nonRevisionTypes = array( RC_LOG );
- wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
+ Hooks::run( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
if ( $nonRevisionTypes ) {
$conds[] = $dbr->makeList(
array(
@@ -288,9 +282,11 @@ class SpecialWatchlist extends ChangesListSpecialPage {
);
}
- protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
+ protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options,
+ &$join_conds, $opts
+ ) {
return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
- && wfRunHooks(
+ && Hooks::run(
'SpecialWatchlistQuery',
array( &$conds, &$tables, &$join_conds, &$fields, $opts ),
'1.23'
@@ -298,9 +294,9 @@ class SpecialWatchlist extends ChangesListSpecialPage {
}
/**
- * Return a DatabaseBase object for reading
+ * Return a IDatabase object for reading
*
- * @return DatabaseBase
+ * @return IDatabase
*/
protected function getDB() {
return wfGetDB( DB_SLAVE, 'watchlist' );
@@ -367,7 +363,9 @@ class SpecialWatchlist extends ChangesListSpecialPage {
$updated = false;
}
- if ( $this->getConfig()->get( 'RCShowWatchingUsers' ) && $user->getOption( 'shownumberswatching' ) ) {
+ if ( $this->getConfig()->get( 'RCShowWatchingUsers' )
+ && $user->getOption( 'shownumberswatching' )
+ ) {
$rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
'COUNT(*)',
array(
@@ -430,7 +428,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
$filters[$key] = $params['msg'];
}
// Disable some if needed
- if ( !$user->useNPPatrol() ) {
+ if ( !$user->useRCPatrol() ) {
unset( $filters['hidepatrolled'] );
}
@@ -503,7 +501,9 @@ class SpecialWatchlist extends ChangesListSpecialPage {
$form .= $this->msg( 'nowatchlist' )->parse() . "\n";
} else {
$form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
- if ( $this->getConfig()->get( 'EnotifWatchlist' ) && $user->getOption( 'enotifwatchlistpages' ) ) {
+ if ( $this->getConfig()->get( 'EnotifWatchlist' )
+ && $user->getOption( 'enotifwatchlistpages' )
+ ) {
$form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
}
if ( $showUpdatedMarker ) {
@@ -562,12 +562,10 @@ class SpecialWatchlist extends ChangesListSpecialPage {
protected function daysLink( $d, $options = array() ) {
$options['days'] = $d;
- $message = $d ? $this->getLanguage()->formatNum( $d )
- : $this->msg( 'watchlistall2' )->escaped();
return Linker::linkKnown(
$this->getPageTitle(),
- $message,
+ $this->getLanguage()->formatNum( $d ),
array(),
$options
);
@@ -581,8 +579,11 @@ class SpecialWatchlist extends ChangesListSpecialPage {
* @return string
*/
protected function cutoffLinks( $days, $options = array() ) {
+ global $wgRCMaxAge;
+ $watchlistMaxDays = ceil( $wgRCMaxAge / ( 3600 * 24 ) );
+
$hours = array( 1, 2, 6, 12 );
- $days = array( 1, 3, 7 );
+ $days = array( 1, 3, 7, $watchlistMaxDays );
$i = 0;
foreach ( $hours as $h ) {
$hours[$i++] = $this->hoursLink( $h, $options );
@@ -594,14 +595,13 @@ class SpecialWatchlist extends ChangesListSpecialPage {
return $this->msg( 'wlshowlast' )->rawParams(
$this->getLanguage()->pipeList( $hours ),
- $this->getLanguage()->pipeList( $days ),
- $this->daysLink( 0, $options ) )->parse();
+ $this->getLanguage()->pipeList( $days ) )->parse();
}
/**
* Count the number of items on a user's watchlist
*
- * @param DatabaseBase $dbr A database connection
+ * @param IDatabase $dbr A database connection
* @return int
*/
protected function countItems( $dbr ) {
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index 7dc6da1f..0b3175a6 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -58,6 +58,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$opts->add( 'hidetrans', false );
$opts->add( 'hidelinks', false );
$opts->add( 'hideimages', false );
+ $opts->add( 'invert', false );
$opts->fetchValuesFromRequest( $this->getRequest() );
$opts->validateIntBounds( 'limit', 0, 5000 );
@@ -72,7 +73,9 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$this->target = Title::newFromURL( $opts->getValue( 'target' ) );
if ( !$this->target ) {
- $out->addHTML( $this->whatlinkshereForm() );
+ if ( !$this->including() ) {
+ $out->addHTML( $this->whatlinkshereForm() );
+ }
return;
}
@@ -125,15 +128,17 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$useLinkNamespaceDBFields = $this->getConfig()->get( 'UseLinkNamespaceDBFields' );
$namespace = $this->opts->getValue( 'namespace' );
+ $invert = $this->opts->getValue( 'invert' );
+ $nsComparison = ( $invert ? '!= ' : '= ' ) . $dbr->addQuotes( $namespace );
if ( is_int( $namespace ) ) {
if ( $useLinkNamespaceDBFields ) {
- $conds['pagelinks']['pl_from_namespace'] = $namespace;
- $conds['templatelinks']['tl_from_namespace'] = $namespace;
- $conds['imagelinks']['il_from_namespace'] = $namespace;
+ $conds['pagelinks'][] = "pl_from_namespace $nsComparison";
+ $conds['templatelinks'][] = "tl_from_namespace $nsComparison";
+ $conds['imagelinks'][] = "il_from_namespace $nsComparison";
} else {
- $conds['pagelinks']['page_namespace'] = $namespace;
- $conds['templatelinks']['page_namespace'] = $namespace;
- $conds['imagelinks']['page_namespace'] = $namespace;
+ $conds['pagelinks'][] = "page_namespace $nsComparison";
+ $conds['templatelinks'][] = "page_namespace $nsComparison";
+ $conds['imagelinks'][] = "page_namespace $nsComparison";
}
}
@@ -149,7 +154,9 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$conds['pagelinks'][] = 'rd_from is NOT NULL';
}
- $queryFunc = function ( $dbr, $table, $fromCol ) use ( $conds, $target, $limit, $useLinkNamespaceDBFields ) {
+ $queryFunc = function ( $dbr, $table, $fromCol ) use (
+ $conds, $target, $limit, $useLinkNamespaceDBFields
+ ) {
// Read an extra row as an at-end check
$queryLimit = $limit + 1;
$on = array(
@@ -174,7 +181,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
);
return $dbr->select(
array( 'page', 'temp_backlink_range' => "($subQuery)" ),
- array( 'page_id', 'page_namespace', 'page_title', 'rd_from' ),
+ array( 'page_id', 'page_namespace', 'page_title', 'rd_from', 'page_is_redirect' ),
array(),
__CLASS__ . '::showIndirectLinks',
array( 'ORDER BY' => 'page_id', 'LIMIT' => $queryLimit ),
@@ -275,7 +282,11 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
if ( $row->rd_from && $level < 2 ) {
$out->addHTML( $this->listItem( $row, $nt, $target, true ) );
- $this->showIndirectLinks( $level + 1, $nt, $this->getConfig()->get( 'MaxRedirectLinksRetrieved' ) );
+ $this->showIndirectLinks(
+ $level + 1,
+ $nt,
+ $this->getConfig()->get( 'MaxRedirectLinksRetrieved' )
+ );
$out->addHTML( Xml::closeElement( 'li' ) );
} else {
$out->addHTML( $this->listItem( $row, $nt, $target ) );
@@ -318,7 +329,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$link = Linker::linkKnown(
$nt,
null,
- array(),
+ $row->page_is_redirect ? array( 'class' => 'mw-redirect' ) : array(),
$query
);
@@ -335,7 +346,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$props[] = $msgcache['isimage'];
}
- wfRunHooks( 'WhatLinksHereProps', array( $row, $nt, $target, &$props ) );
+ Hooks::run( 'WhatLinksHereProps', array( $row, $nt, $target, &$props ) );
if ( count( $props ) ) {
$propsText = $this->msg( 'parentheses' )
@@ -419,6 +430,7 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$target = $this->target ? $this->target->getPrefixedText() : '';
$namespace = $this->opts->consumeValue( 'namespace' );
+ $nsinvert = $this->opts->consumeValue( 'invert' );
# Build up the form
$f = Xml::openElement( 'form', array( 'action' => wfScript() ) );
@@ -431,9 +443,9 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
$f .= Xml::fieldset( $this->msg( 'whatlinkshere' )->text() );
- # Target input
+ # Target input (.mw-searchInput enables suggestions)
$f .= Xml::inputLabel( $this->msg( 'whatlinkshere-page' )->text(), 'target',
- 'mw-whatlinkshere-target', 40, $target );
+ 'mw-whatlinkshere-target', 40, $target, array( 'class' => 'mw-searchInput' ) );
$f .= ' ';
@@ -450,6 +462,15 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
)
);
+ $f .= '&#160;' .
+ Xml::checkLabel(
+ $this->msg( 'invert' )->text(),
+ 'invert',
+ 'nsinvert',
+ $nsinvert,
+ array( 'title' => $this->msg( 'tooltip-whatlinkshere-invert' )->text() )
+ );
+
$f .= ' ';
# Submit
@@ -496,6 +517,24 @@ class SpecialWhatLinksHere extends IncludableSpecialPage {
);
}
+ /**
+ * Return an array of subpages beginning with $search that this special page will accept.
+ *
+ * @param string $search Prefix to search for
+ * @param int $limit Maximum number of results to return (usually 10)
+ * @param int $offset Number of results to skip (usually 0)
+ * @return string[] Matching subpages
+ */
+ public function prefixSearchSubpages( $search, $limit, $offset ) {
+ if ( $search === '' ) {
+ return array();
+ }
+ // Autocomplete subpage the same as a normal search
+ $prefixSearcher = new StringPrefixSearch;
+ $result = $prefixSearcher->search( $search, $limit, array(), $offset );
+ return $result;
+ }
+
protected function getGroupName() {
return 'pagetools';
}
diff --git a/includes/templates/NoLocalSettings.mustache b/includes/templates/NoLocalSettings.mustache
new file mode 100644
index 00000000..54579491
--- /dev/null
+++ b/includes/templates/NoLocalSettings.mustache
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+ <head>
+ <meta charset="UTF-8" />
+ <title>MediaWiki {{wgVersion}}</title>
+ <style media="screen">
+ html, body {
+ color: #000;
+ background-color: #fff;
+ font-family: sans-serif;
+ text-align: center;
+ }
+
+ h1 {
+ font-size: 150%;
+ }
+ </style>
+ </head>
+ <body>
+ <img src="{{path}}resources/assets/mediawiki.png" alt="The MediaWiki logo" />
+
+ <h1>MediaWiki {{wgVersion}}</h1>
+ <div class="error">
+ {{#localSettingsExists}}
+ <p>LocalSettings.php not readable.</p>
+ <p>Please correct file permissions and try again.</p>
+ {{/localSettingsExists}}
+ {{^localSettingsExists}}
+ <p>LocalSettings.php not found.</p>
+ {{#installerStarted}}
+ <p>Please <a href="{{path}}mw-config/index.{{ext}}">complete the installation</a> and download LocalSettings.php.</p>
+ {{/installerStarted}}
+ {{^installerStarted}}
+ <p>Please <a href="{{path}}mw-config/index.{{ext}}">set up the wiki</a> first.</p>
+ {{/installerStarted}}
+ {{/localSettingsExists}}
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/includes/templates/Usercreate.php b/includes/templates/Usercreate.php
index 01da0bd7..f09b6bba 100644
--- a/includes/templates/Usercreate.php
+++ b/includes/templates/Usercreate.php
@@ -53,7 +53,7 @@ class UsercreateTemplate extends BaseTemplate {
<div id="userloginForm">
<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 */ ?>
+ <?php $this->html( 'header' ); ?>
</section>
<!-- This element is used by the mediawiki.special.userlogin.signup.js module. -->
<div
@@ -217,8 +217,10 @@ class UsercreateTemplate extends BaseTemplate {
<?php if ( !empty( $inputItem['value'] ) ) {
echo 'checked="checked"';
} ?>
- ><label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"></label>
- </div><?php $this->msgHtml( $inputItem['msg'] ); ?>
+ ><label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>">
+ <?php $this->msg( $inputItem['msg'] ); ?>
+ </label>
+ </div>
<?php
} else {
// Not a checkbox.
@@ -248,20 +250,25 @@ class UsercreateTemplate extends BaseTemplate {
}
}
- // JS attempts to move the image CAPTCHA below this part of the form,
- // so skip one index.
+ // A separate placeholder for any inserting any extrafields, e.g used by ConfirmEdit extension
+ if ( $this->haveData( 'extrafields' ) ) {
+ echo $this->data['extrafields'];
+ }
+ // skip one index.
$tabIndex++;
?>
<div class="mw-ui-vform-field mw-submit">
<?php
- echo Html::input(
- 'wpCreateaccount',
+ echo Html::submitButton(
$this->getMsg( $this->data['loggedin'] ? 'createacct-another-submit' : 'createacct-submit' ),
- 'submit',
array(
- 'class' => "mw-ui-button mw-ui-big mw-ui-block mw-ui-constructive",
'id' => 'wpCreateaccount',
+ 'name' => 'wpCreateaccount',
'tabindex' => $tabIndex++
+ ),
+ array(
+ 'mw-ui-block',
+ 'mw-ui-constructive',
)
);
?>
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 8bba4265..345bb71b 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -70,12 +70,10 @@ class UserloginTemplate extends BaseTemplate {
?>
</label>
<?php
- $extraAttrs = array();
echo Html::input( 'wpName', $this->data['name'], 'text', array(
'class' => 'loginText mw-ui-input',
'id' => 'wpName1',
'tabindex' => '1',
- 'size' => '20',
// 'required' is blacklisted for now in Html.php due to browser issues.
// Keeping here in case that changes.
'required' => true,
@@ -90,14 +88,6 @@ class UserloginTemplate extends BaseTemplate {
<label for='wpPassword1'>
<?php
$this->msg( 'userlogin-yourpassword' );
-
- if ( $this->data['useemail'] && $this->data['canreset'] && $this->data['resetlink'] === true ) {
- echo ' ' . Linker::link(
- SpecialPage::getTitleFor( 'PasswordReset' ),
- $this->getMsg( 'userlogin-resetpassword-link' )->parse(),
- array( 'class' => 'mw-ui-flush-right' )
- );
- }
?>
</label>
<?php
@@ -105,7 +95,6 @@ class UserloginTemplate extends BaseTemplate {
'class' => 'loginPassword mw-ui-input',
'id' => 'wpPassword1',
'tabindex' => '2',
- 'size' => '20',
// Set focus to this field if username is filled in.
'autofocus' => (bool)$this->data['name'],
'placeholder' => $this->getMsg( 'userlogin-yourpassword-ph' )->text()
@@ -148,15 +137,19 @@ class UserloginTemplate extends BaseTemplate {
<div class="mw-ui-vform-field">
<?php
- echo Html::input( 'wpLoginAttempt', $this->getMsg( 'pt-login-button' )->text(), 'submit', array(
+ $attrs = array(
'id' => 'wpLoginAttempt',
+ 'name' => 'wpLoginAttempt',
'tabindex' => '6',
- 'class' => 'mw-ui-button mw-ui-big mw-ui-block mw-ui-constructive'
- ) );
+ );
+ $modifiers = array(
+ 'mw-ui-constructive',
+ );
+ echo Html::submitButton( $this->getMsg( 'pt-login-button' )->text(), $attrs, $modifiers );
?>
</div>
- <div class="mw-ui-vform-field" id="mw-userlogin-help">
+ <div class="mw-ui-vform-field mw-form-related-link-container" id="mw-userlogin-help">
<?php
echo Html::element(
'a',
@@ -169,21 +162,51 @@ class UserloginTemplate extends BaseTemplate {
);
?>
</div>
+ <?php
- <?php if ( $this->haveData( 'createOrLoginHref' ) ) { ?>
- <?php if ( $this->data['loggedin'] ) { ?>
- <div id="mw-createaccount-another">
- <a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7" class="mw-ui-button"><?php $this->msg( 'userlogin-createanother' ); ?></a>
+ if ( $this->data['useemail'] && $this->data['canreset'] && $this->data['resetlink'] === true ) {
+ echo Html::rawElement(
+ 'div',
+ array(
+ 'class' => 'mw-ui-vform-field mw-form-related-link-container',
+ ),
+ Linker::link(
+ SpecialPage::getTitleFor( 'PasswordReset' ),
+ $this->getMsg( 'userlogin-resetpassword-link' )->escaped()
+ )
+ );
+ }
+
+ if ( $this->haveData( 'createOrLoginHref' ) ) {
+ if ( $this->data['loggedin'] ) { ?>
+ <div class="mw-form-related-link-container mw-ui-vform-field">
+ <a href="<?php $this->text( 'createOrLoginHref' ); ?>" id="mw-createaccount-join" tabindex="7"><?php $this->msg( 'userlogin-createanother' ); ?></a>
</div>
<?php } else { ?>
- <div id="mw-createaccount-cta">
- <?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 id="mw-createaccount-cta" class="mw-form-related-link-container mw-ui-vform-field">
+ <?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 } ?>
- <?php if ( $this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
- <?php if ( $this->haveData( 'token' ) ) { ?><input type="hidden" name="wpLoginToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
- <?php if ( $this->data['cansecurelogin'] ) {?><input type="hidden" name="wpForceHttps" value="<?php $this->text( 'stickhttps' ); ?>" /><?php } ?>
+ <?php
+ }
+ }
+
+ // Hidden fields
+ $fields = '';
+ if ( $this->haveData( 'uselang' ) ) {
+ $fields .= Html::hidden( 'uselang', $this->data['uselang'] );
+ }
+ if ( $this->haveData( 'token' ) ) {
+ $fields .= Html::hidden( 'wpLoginToken', $this->data['token'] );
+ }
+ if ( $this->data['cansecurelogin'] ) {
+ $fields .= Html::hidden( 'wpForceHttps', $this->data['stickhttps'] );
+ }
+ if ( $this->data['cansecurelogin'] && $this->haveData( 'fromhttp' ) ) {
+ $fields .= Html::hidden( 'wpFromhttp', $this->data['fromhttp'] );
+ }
+ echo $fields;
+
+ ?>
</form>
</div>
</div>
diff --git a/includes/title/ForeignTitle.php b/includes/title/ForeignTitle.php
new file mode 100644
index 00000000..ed96d17c
--- /dev/null
+++ b/includes/title/ForeignTitle.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * A structure to hold the title of a page on a foreign MediaWiki installation
+ *
+ * This 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 This, that and the other
+ */
+
+/**
+ * A simple, immutable structure to hold the title of a page on a foreign
+ * MediaWiki installation.
+ */
+class ForeignTitle {
+ /**
+ * @var int|null
+ * Null if we don't know the namespace ID (e.g. interwiki links)
+ */
+ protected $namespaceId;
+ /** @var string */
+ protected $namespaceName;
+ /** @var string */
+ protected $pageName;
+
+ /**
+ * Creates a new ForeignTitle object.
+ *
+ * @param int|null $namespaceId Null if the namespace ID is unknown (e.g.
+ * interwiki links)
+ * @param string $namespaceName
+ * @param string $pageName
+ */
+ public function __construct( $namespaceId, $namespaceName, $pageName ) {
+ if ( is_null( $namespaceId ) ) {
+ $this->namespaceId = null;
+ } else {
+ $this->namespaceId = intval( $namespaceId );
+ }
+ $this->namespaceName = str_replace( ' ', '_', $namespaceName );
+ $this->pageName = str_replace( ' ', '_', $pageName );
+ }
+
+ /**
+ * Do we know the namespace ID of the page on the foreign wiki?
+ * @return bool
+ */
+ public function isNamespaceIdKnown() {
+ return !is_null( $this->namespaceId );
+ }
+
+ /**
+ * @return int
+ * @throws MWException If isNamespaceIdKnown() is false, it does not make
+ * sense to call this function.
+ */
+ public function getNamespaceId() {
+ if ( is_null( $this->namespaceId ) ) {
+ throw new MWException(
+ "Attempted to call getNamespaceId when the namespace ID is not known" );
+ }
+ return $this->namespaceId;
+ }
+
+ /** @return string */
+ public function getNamespaceName() {
+ return $this->namespaceName;
+ }
+
+ /** @return string */
+ public function getText() {
+ return $this->pageName;
+ }
+
+ /** @return string */
+ public function getFullText() {
+ $result = '';
+ if ( $this->namespaceName ) {
+ $result .= $this->namespaceName . ':';
+ }
+ $result .= $this->pageName;
+ return $result;
+ }
+
+ /**
+ * Returns a string representation of the title, for logging. This is purely
+ * informative and must not be used programmatically. Use the appropriate
+ * ImportTitleFactory to generate the correct string representation for a
+ * given use.
+ *
+ * @return string
+ */
+ public function __toString() {
+ $name = '';
+ if ( $this->isNamespaceIdKnown() ) {
+ $name .= '{ns' . $this->namespaceId . '}';
+ } else {
+ $name .= '{ns??}';
+ }
+ $name .= $this->namespaceName . ':' . $this->pageName;
+
+ return $name;
+ }
+}
diff --git a/includes/title/ForeignTitleFactory.php b/includes/title/ForeignTitleFactory.php
new file mode 100644
index 00000000..427afdf3
--- /dev/null
+++ b/includes/title/ForeignTitleFactory.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
+ * @license GPL 2+
+ */
+
+/**
+ * A parser that translates page titles into ForeignTitle objects.
+ */
+interface ForeignTitleFactory {
+ /**
+ * Creates a ForeignTitle object based on the page title, and optionally the
+ * namespace ID, of a page on a foreign wiki. These values could be, for
+ * example, the <title> and <ns> attributes found in an XML dump.
+ *
+ * @param string $title The page title
+ * @param int|null $ns The namespace ID, or null if this data is not available
+ * @return ForeignTitle
+ */
+ public function createForeignTitle( $title, $ns = null );
+}
diff --git a/includes/title/ImportTitleFactory.php b/includes/title/ImportTitleFactory.php
new file mode 100644
index 00000000..629616d8
--- /dev/null
+++ b/includes/title/ImportTitleFactory.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
+ * @license GPL 2+
+ */
+
+/**
+ * Represents an object that can convert page titles on a foreign wiki
+ * (ForeignTitle objects) into page titles on the local wiki (Title objects).
+ */
+interface ImportTitleFactory {
+ /**
+ * Determines which local title best corresponds to the given foreign title.
+ * If such a title can't be found or would be locally invalid, null is
+ * returned.
+ *
+ * @param ForeignTitle $foreignTitle The ForeignTitle to convert
+ * @return Title|null
+ */
+ public function createTitleFromForeignTitle( ForeignTitle $foreignTitle );
+}
diff --git a/includes/title/MalformedTitleException.php b/includes/title/MalformedTitleException.php
index a8a5d754..a9e58b3e 100644
--- a/includes/title/MalformedTitleException.php
+++ b/includes/title/MalformedTitleException.php
@@ -27,6 +27,7 @@
*
* @license GPL 2+
* @author Daniel Kinzler
+ * @since 1.23
*/
class MalformedTitleException extends Exception {
}
diff --git a/includes/title/MediaWikiPageLinkRenderer.php b/includes/title/MediaWikiPageLinkRenderer.php
index f46cb5e3..9ee48419 100644
--- a/includes/title/MediaWikiPageLinkRenderer.php
+++ b/includes/title/MediaWikiPageLinkRenderer.php
@@ -26,6 +26,7 @@
* A service for generating links from page titles.
*
* @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ * @since 1.23
*/
class MediaWikiPageLinkRenderer implements PageLinkRenderer {
/**
diff --git a/includes/title/MediaWikiTitleCodec.php b/includes/title/MediaWikiTitleCodec.php
index 6ca0799c..20034b74 100644
--- a/includes/title/MediaWikiTitleCodec.php
+++ b/includes/title/MediaWikiTitleCodec.php
@@ -31,6 +31,7 @@
* via parseTitle() or from a (semi)trusted source, such as the database.
*
* @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ * @since 1.23
*/
class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
/**
@@ -229,7 +230,7 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
);
$dbkey = trim( $dbkey, '_' );
- if ( strpos( $dbkey, UTF8_REPLACEMENT ) !== false ) {
+ if ( strpos( $dbkey, UtfNormal\Constants::UTF8_REPLACEMENT ) !== false ) {
# Contained illegal UTF-8 sequences or forbidden Unicode chars.
throw new MalformedTitleException( 'Bad UTF-8 sequences found in title: ' . $text );
}
@@ -322,7 +323,7 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
}
# Reject illegal characters.
- $rxTc = Title::getTitleInvalidRegex();
+ $rxTc = self::getTitleInvalidRegex();
if ( preg_match( $rxTc, $dbkey ) ) {
throw new MalformedTitleException( 'Illegal characters found in title: ' . $text );
}
@@ -397,4 +398,33 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
return $parts;
}
+
+ /**
+ * Returns a simple regex that will match on characters and sequences invalid in titles.
+ * 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.
+ * Previously Title::getTitleInvalidRegex()
+ *
+ * @return string Regex string
+ * @since 1.25
+ */
+ public static function getTitleInvalidRegex() {
+ static $rxTc = false;
+ if ( !$rxTc ) {
+ # Matching titles will be held as illegal.
+ $rxTc = '/' .
+ # Any character not allowed is forbidden...
+ '[^' . Title::legalChars() . ']' .
+ # URL percent encoding sequences interfere with the ability
+ # to round-trip titles -- you can't link to them consistently.
+ '|%[0-9A-Fa-f]{2}' .
+ # XML/HTML character references produce similar issues.
+ '|&[A-Za-z0-9\x80-\xff]+;' .
+ '|&#[0-9]+;' .
+ '|&#x[0-9A-Fa-f]+;' .
+ '/S';
+ }
+
+ return $rxTc;
+ }
}
diff --git a/includes/title/NaiveForeignTitleFactory.php b/includes/title/NaiveForeignTitleFactory.php
new file mode 100644
index 00000000..6c8bcc04
--- /dev/null
+++ b/includes/title/NaiveForeignTitleFactory.php
@@ -0,0 +1,71 @@
+<?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
+ * @license GPL 2+
+ */
+
+/**
+ * A parser that translates page titles on a foreign wiki into ForeignTitle
+ * objects, with no knowledge of the namespace setup on the foreign site.
+ */
+class NaiveForeignTitleFactory implements ForeignTitleFactory {
+ /**
+ * Creates a ForeignTitle object based on the page title, and optionally the
+ * namespace ID, of a page on a foreign wiki. These values could be, for
+ * example, the <title> and <ns> attributes found in an XML dump.
+ *
+ * Although exported XML dumps have contained a map of namespace IDs to names
+ * since MW 1.5, the importer used to completely ignore the <siteinfo> tag
+ * before MW 1.25. It is therefore possible that custom XML dumps (i.e. not
+ * generated by Special:Export) have been created without this metadata.
+ * As a result, this code falls back to using namespace data for the local
+ * wiki (similar to buggy pre-1.25 behaviour) if $ns is not supplied.
+ *
+ * @param string $title The page title
+ * @param int|null $ns The namespace ID, or null if this data is not available
+ * @return ForeignTitle
+ */
+ public function createForeignTitle( $title, $ns = null ) {
+ $pieces = explode( ':', $title, 2 );
+
+ global $wgContLang;
+
+ // Can we assume that the part of the page title before the colon is a
+ // namespace name?
+ //
+ // XML export schema version 0.5 and earlier (MW 1.18 and earlier) does not
+ // contain a <ns> tag, so we need to be able to handle that case.
+ //
+ // If we know the namespace ID, we assume a non-zero namespace ID means
+ // the ':' sets off a valid namespace name. If we don't know the namespace
+ // ID, we fall back to using the local wiki's namespace names to resolve
+ // this -- better than nothing, and mimics the old crappy behavior
+ $isNamespacePartValid = is_null( $ns ) ?
+ ( $wgContLang->getNsIndex( $pieces[0] ) !== false ) :
+ $ns != 0;
+
+ if ( count( $pieces ) === 2 && $isNamespacePartValid ) {
+ list( $namespaceName, $pageName ) = $pieces;
+ } else {
+ $namespaceName = '';
+ $pageName = $title;
+ }
+
+ return new ForeignTitle( $ns, $namespaceName, $pageName );
+ }
+}
diff --git a/includes/title/NaiveImportTitleFactory.php b/includes/title/NaiveImportTitleFactory.php
new file mode 100644
index 00000000..43c662e7
--- /dev/null
+++ b/includes/title/NaiveImportTitleFactory.php
@@ -0,0 +1,65 @@
+<?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
+ * @license GPL 2+
+ */
+
+/**
+ * A class to convert page titles on a foreign wiki (ForeignTitle objects) into
+ * page titles on the local wiki (Title objects), using a default namespace
+ * mapping.
+ *
+ * For built-in namespaces (0 <= ID < 100), we try to find a local namespace
+ * with the same namespace ID as the foreign page. If no such namespace exists,
+ * or the namespace ID is unknown or > 100, we look for a local namespace with
+ * a matching namespace name. If that can't be found, we dump the page in the
+ * main namespace as a last resort.
+ */
+class NaiveImportTitleFactory implements ImportTitleFactory {
+ /**
+ * Determines which local title best corresponds to the given foreign title.
+ * If such a title can't be found or would be locally invalid, null is
+ * returned.
+ *
+ * @param ForeignTitle $foreignTitle The ForeignTitle to convert
+ * @return Title|null
+ */
+ public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) {
+ global $wgContLang;
+
+ if ( $foreignTitle->isNamespaceIdKnown() ) {
+ $foreignNs = $foreignTitle->getNamespaceId();
+
+ // For built-in namespaces (0 <= ID < 100), we try to find a local NS with
+ // the same namespace ID
+ if ( $foreignNs < 100 && MWNamespace::exists( $foreignNs ) ) {
+ return Title::makeTitleSafe( $foreignNs, $foreignTitle->getText() );
+ }
+ }
+
+ // Do we have a local namespace by the same name as the foreign
+ // namespace?
+ $targetNs = $wgContLang->getNsIndex( $foreignTitle->getNamespaceName() );
+ if ( $targetNs !== false ) {
+ return Title::makeTitleSafe( $targetNs, $foreignTitle->getText() );
+ }
+
+ // Otherwise, just fall back to main namespace
+ return Title::makeTitleSafe( 0, $foreignTitle->getFullText() );
+ }
+}
diff --git a/includes/title/NamespaceAwareForeignTitleFactory.php b/includes/title/NamespaceAwareForeignTitleFactory.php
new file mode 100644
index 00000000..bf97e2cd
--- /dev/null
+++ b/includes/title/NamespaceAwareForeignTitleFactory.php
@@ -0,0 +1,134 @@
+<?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
+ * @license GPL 2+
+ */
+
+/**
+ * A parser that translates page titles on a foreign wiki into ForeignTitle
+ * objects, using information about the namespace setup on the foreign site.
+ */
+class NamespaceAwareForeignTitleFactory implements ForeignTitleFactory {
+ /**
+ * @var array
+ */
+ protected $foreignNamespaces;
+ /**
+ * @var array
+ */
+ private $foreignNamespacesFlipped;
+
+ /**
+ * Normalizes an array name for $foreignNamespacesFlipped.
+ * @param string $name
+ * @return string
+ */
+ private function normalizeNamespaceName( $name ) {
+ return strtolower( str_replace( ' ', '_', $name ) );
+ }
+
+ /**
+ * @param array|null $foreignNamespaces An array 'id' => 'name' which contains
+ * the complete namespace setup of the foreign wiki. Such data could be
+ * obtained from siteinfo/namespaces in an XML dump file, or by an action API
+ * query such as api.php?action=query&meta=siteinfo&siprop=namespaces. If
+ * this data is unavailable, use NaiveForeignTitleFactory instead.
+ */
+ public function __construct( $foreignNamespaces ) {
+ $this->foreignNamespaces = $foreignNamespaces;
+ if ( !is_null( $foreignNamespaces ) ) {
+ $this->foreignNamespacesFlipped = array();
+ foreach ( $foreignNamespaces as $id => $name ) {
+ $newKey = self::normalizeNamespaceName( $name );
+ $this->foreignNamespacesFlipped[$newKey] = $id;
+ }
+ }
+ }
+
+ /**
+ * Creates a ForeignTitle object based on the page title, and optionally the
+ * namespace ID, of a page on a foreign wiki. These values could be, for
+ * example, the <title> and <ns> attributes found in an XML dump.
+ *
+ * @param string $title The page title
+ * @param int|null $ns The namespace ID, or null if this data is not available
+ * @return ForeignTitle
+ */
+ public function createForeignTitle( $title, $ns = null ) {
+ // Export schema version 0.5 and earlier (MW 1.18 and earlier) does not
+ // contain a <ns> tag, so we need to be able to handle that case.
+ if ( is_null( $ns ) ) {
+ return self::parseTitleNoNs( $title );
+ } else {
+ return self::parseTitleWithNs( $title, $ns );
+ }
+ }
+
+ /**
+ * Helper function to parse the title when the namespace ID is not specified.
+ *
+ * @param string $title
+ * @return ForeignTitle
+ */
+ protected function parseTitleNoNs( $title ) {
+ $pieces = explode( ':', $title, 2 );
+ $key = self::normalizeNamespaceName( $pieces[0] );
+
+ // Does the part before the colon match a known namespace? Check the
+ // foreign namespaces
+ $isNamespacePartValid = isset( $this->foreignNamespacesFlipped[$key] );
+
+ if ( count( $pieces ) === 2 && $isNamespacePartValid ) {
+ list( $namespaceName, $pageName ) = $pieces;
+ $ns = $this->foreignNamespacesFlipped[$key];
+ } else {
+ $namespaceName = '';
+ $pageName = $title;
+ $ns = 0;
+ }
+
+ return new ForeignTitle( $ns, $namespaceName, $pageName );
+ }
+
+ /**
+ * Helper function to parse the title when the namespace value is known.
+ *
+ * @param string $title
+ * @param int $ns
+ * @return ForeignTitle
+ */
+ protected function parseTitleWithNs( $title, $ns ) {
+ $pieces = explode( ':', $title, 2 );
+
+ if ( isset( $this->foreignNamespaces[$ns] ) ) {
+ $namespaceName = $this->foreignNamespaces[$ns];
+ } else {
+ $namespaceName = $ns == '0' ? '' : $pieces[0];
+ }
+
+ // We assume that the portion of the page title before the colon is the
+ // namespace name, except in the case of namespace 0
+ if ( $ns != '0' ) {
+ $pageName = $pieces[1];
+ } else {
+ $pageName = $title;
+ }
+
+ return new ForeignTitle( $ns, $namespaceName, $pageName );
+ }
+}
diff --git a/includes/title/NamespaceImportTitleFactory.php b/includes/title/NamespaceImportTitleFactory.php
new file mode 100644
index 00000000..0c1d0c40
--- /dev/null
+++ b/includes/title/NamespaceImportTitleFactory.php
@@ -0,0 +1,52 @@
+<?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
+ * @license GPL 2+
+ */
+
+/**
+ * A class to convert page titles on a foreign wiki (ForeignTitle objects) into
+ * page titles on the local wiki (Title objects), placing all pages in a fixed
+ * local namespace.
+ */
+class NamespaceImportTitleFactory implements ImportTitleFactory {
+ /** @var int */
+ protected $ns;
+
+ /**
+ * @param int $ns The namespace to use for all pages
+ */
+ public function __construct( $ns ) {
+ if ( !MWNamespace::exists( $ns ) ) {
+ throw new MWException( "Namespace $ns doesn't exist on this wiki" );
+ }
+ $this->ns = $ns;
+ }
+
+ /**
+ * Determines which local title best corresponds to the given foreign title.
+ * If such a title can't be found or would be locally invalid, null is
+ * returned.
+ *
+ * @param ForeignTitle $foreignTitle The ForeignTitle to convert
+ * @return Title|null
+ */
+ public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) {
+ return Title::makeTitleSafe( $this->ns, $foreignTitle->getText() );
+ }
+}
diff --git a/includes/title/PageLinkRenderer.php b/includes/title/PageLinkRenderer.php
index fb1096e0..ca91f583 100644
--- a/includes/title/PageLinkRenderer.php
+++ b/includes/title/PageLinkRenderer.php
@@ -29,6 +29,7 @@
* URLs, and how links are encoded in a given output format.
*
* @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ * @since 1.23
*/
interface PageLinkRenderer {
/**
diff --git a/includes/title/SubpageImportTitleFactory.php b/includes/title/SubpageImportTitleFactory.php
new file mode 100644
index 00000000..b0be7afa
--- /dev/null
+++ b/includes/title/SubpageImportTitleFactory.php
@@ -0,0 +1,55 @@
+<?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
+ * @license GPL 2+
+ */
+
+/**
+ * A class to convert page titles on a foreign wiki (ForeignTitle objects) into
+ * page titles on the local wiki (Title objects), placing all pages as subpages
+ * of a given root page.
+ */
+class SubpageImportTitleFactory implements ImportTitleFactory {
+ /** @var Title */
+ protected $rootPage;
+
+ /**
+ * @param Title $rootPage The root page under which all pages should be
+ * created
+ */
+ public function __construct( Title $rootPage ) {
+ if ( !MWNamespace::hasSubpages( $rootPage->getNamespace() ) ) {
+ throw new MWException( "The root page you specified, $rootPage, is in a " .
+ "namespace where subpages are not allowed" );
+ }
+ $this->rootPage = $rootPage;
+ }
+
+ /**
+ * Determines which local title best corresponds to the given foreign title.
+ * If such a title can't be found or would be locally invalid, null is
+ * returned.
+ *
+ * @param ForeignTitle $foreignTitle The ForeignTitle to convert
+ * @return Title|null
+ */
+ public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) {
+ return Title::newFromText( $this->rootPage->getPrefixedDBkey() . '/' .
+ $foreignTitle->getFullText() );
+ }
+}
diff --git a/includes/title/TitleFormatter.php b/includes/title/TitleFormatter.php
index 7c71ef5e..aad83769 100644
--- a/includes/title/TitleFormatter.php
+++ b/includes/title/TitleFormatter.php
@@ -29,6 +29,7 @@
* forms to be used in the database, in urls, in wikitext, etc.
*
* @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ * @since 1.23
*/
interface TitleFormatter {
/**
diff --git a/includes/title/TitleParser.php b/includes/title/TitleParser.php
index 0635ee86..381b1d09 100644
--- a/includes/title/TitleParser.php
+++ b/includes/title/TitleParser.php
@@ -29,6 +29,7 @@
* forms to be used in the database, in urls, in wikitext, etc.
*
* @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ * @since 1.23
*/
interface TitleParser {
/**
diff --git a/includes/title/TitleValue.php b/includes/title/TitleValue.php
index 402247c2..5cac3470 100644
--- a/includes/title/TitleValue.php
+++ b/includes/title/TitleValue.php
@@ -32,6 +32,7 @@
* It does not represent a link, and does not support interwiki prefixes etc.
*
* @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
+ * @since 1.23
*/
class TitleValue {
/**
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index 14959c26..6da8250b 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -69,8 +69,6 @@ abstract class UploadBase {
const WINDOWS_NONASCII_FILENAME = 13;
const FILENAME_TOO_LONG = 14;
- const SESSION_STATUS_KEY = 'wsUploadStatusData';
-
/**
* @param int $error
* @return string
@@ -152,7 +150,7 @@ abstract class UploadBase {
// Give hooks the chance to handle this request
$className = null;
- wfRunHooks( 'UploadCreateFromRequest', array( $type, &$className ) );
+ Hooks::run( 'UploadCreateFromRequest', array( $type, &$className ) );
if ( is_null( $className ) ) {
$className = 'UploadFrom' . $type;
wfDebug( __METHOD__ . ": class name: $className\n" );
@@ -263,7 +261,6 @@ abstract class UploadBase {
* @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
@@ -277,7 +274,6 @@ abstract class UploadBase {
} else {
$path = $srcPath;
}
- wfProfileOut( __METHOD__ );
return $path;
}
@@ -287,13 +283,11 @@ abstract class UploadBase {
* @return mixed Const self::OK or else an array with error information
*/
public function verifyUpload() {
- wfProfileIn( __METHOD__ );
/**
* If there was no filename or a zero size given, give up quick.
*/
if ( $this->isEmptyFile() ) {
- wfProfileOut( __METHOD__ );
return array( 'status' => self::EMPTY_FILE );
}
@@ -303,7 +297,6 @@ abstract class UploadBase {
*/
$maxSize = self::getMaxUploadSize( $this->getSourceType() );
if ( $this->mFileSize > $maxSize ) {
- wfProfileOut( __METHOD__ );
return array(
'status' => self::FILE_TOO_LARGE,
@@ -318,7 +311,6 @@ abstract class UploadBase {
*/
$verification = $this->verifyFile();
if ( $verification !== true ) {
- wfProfileOut( __METHOD__ );
return array(
'status' => self::VERIFICATION_ERROR,
@@ -331,22 +323,18 @@ abstract class UploadBase {
*/
$result = $this->validateName();
if ( $result !== true ) {
- wfProfileOut( __METHOD__ );
return $result;
}
$error = '';
- if ( !wfRunHooks( 'UploadVerification',
+ if ( !Hooks::run( 'UploadVerification',
array( $this->mDestName, $this->mTempPath, &$error ) )
) {
- wfProfileOut( __METHOD__ );
return array( 'status' => self::HOOK_ABORTED, 'error' => $error );
}
- wfProfileOut( __METHOD__ );
-
return array( 'status' => self::OK );
}
@@ -388,12 +376,10 @@ abstract class UploadBase {
*/
protected function verifyMimeType( $mime ) {
global $wgVerifyMimeType;
- wfProfileIn( __METHOD__ );
if ( $wgVerifyMimeType ) {
wfDebug( "mime: <$mime> extension: <{$this->mFinalExtension}>\n" );
global $wgMimeTypeBlacklist;
if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
- wfProfileOut( __METHOD__ );
return array( 'filetype-badmime', $mime );
}
@@ -408,15 +394,12 @@ abstract class UploadBase {
$ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime );
foreach ( $ieTypes as $ieType ) {
if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
- wfProfileOut( __METHOD__ );
return array( 'filetype-bad-ie-mime', $ieType );
}
}
}
- wfProfileOut( __METHOD__ );
-
return true;
}
@@ -426,12 +409,10 @@ abstract class UploadBase {
* @return mixed True of the file is verified, array otherwise.
*/
protected function verifyFile() {
- global $wgVerifyMimeType;
- wfProfileIn( __METHOD__ );
+ global $wgVerifyMimeType, $wgDisableUploadScriptChecks;
$status = $this->verifyPartialFile();
if ( $status !== true ) {
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -442,32 +423,39 @@ abstract class UploadBase {
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 );
}
}
+ # check for htmlish code and javascript
+ if ( !$wgDisableUploadScriptChecks ) {
+ if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
+ $svgStatus = $this->detectScriptInSvg( $this->mTempPath, false );
+ if ( $svgStatus !== false ) {
+
+ return $svgStatus;
+ }
+ }
+ }
+
$handler = MediaHandler::getHandler( $mime );
if ( $handler ) {
$handlerStatus = $handler->verifyUpload( $this->mTempPath );
if ( !$handlerStatus->isOK() ) {
$errors = $handlerStatus->getErrorsArray();
- wfProfileOut( __METHOD__ );
return reset( $errors );
}
}
- wfRunHooks( 'UploadVerifyFile', array( $this, $mime, &$status ) );
+ Hooks::run( 'UploadVerifyFile', array( $this, $mime, &$status ) );
if ( $status !== true ) {
- wfProfileOut( __METHOD__ );
return $status;
}
wfDebug( __METHOD__ . ": all clear; passing.\n" );
- wfProfileOut( __METHOD__ );
return true;
}
@@ -482,7 +470,6 @@ abstract class UploadBase {
*/
protected function verifyPartialFile() {
global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
- wfProfileIn( __METHOD__ );
# getTitle() sets some internal parameters like $this->mFinalExtension
$this->getTitle();
@@ -493,7 +480,6 @@ abstract class UploadBase {
$mime = $this->mFileProps['file-mime'];
$status = $this->verifyMimeType( $mime );
if ( $status !== true ) {
- wfProfileOut( __METHOD__ );
return $status;
}
@@ -501,14 +487,12 @@ abstract class UploadBase {
# check for htmlish code and javascript
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 );
+ $svgStatus = $this->detectScriptInSvg( $this->mTempPath, true );
if ( $svgStatus !== false ) {
- wfProfileOut( __METHOD__ );
return $svgStatus;
}
@@ -525,13 +509,11 @@ abstract class UploadBase {
$errors = $zipStatus->getErrorsArray();
$error = reset( $errors );
if ( $error[0] !== 'zip-wrong-format' ) {
- wfProfileOut( __METHOD__ );
return $error;
}
}
if ( $this->mJavaDetected ) {
- wfProfileOut( __METHOD__ );
return array( 'uploadjava' );
}
@@ -540,13 +522,10 @@ abstract class UploadBase {
# Scan the uploaded file for viruses
$virus = $this->detectVirus( $this->mTempPath );
if ( $virus ) {
- wfProfileOut( __METHOD__ );
return array( 'uploadvirus', $virus );
}
- wfProfileOut( __METHOD__ );
-
return true;
}
@@ -639,11 +618,11 @@ abstract class UploadBase {
*/
public function checkWarnings() {
global $wgLang;
- wfProfileIn( __METHOD__ );
$warnings = array();
$localFile = $this->getLocalFile();
+ $localFile->load( File::READ_LATEST );
$filename = $localFile->getName();
/**
@@ -699,17 +678,15 @@ abstract class UploadBase {
}
// Check dupes against archives
- $archivedImage = new ArchivedFile( null, 0, "{$hash}.{$this->mFinalExtension}" );
- if ( $archivedImage->getID() > 0 ) {
- if ( $archivedImage->userCan( File::DELETED_FILE ) ) {
- $warnings['duplicate-archive'] = $archivedImage->getName();
+ $archivedFile = new ArchivedFile( null, 0, '', $hash );
+ if ( $archivedFile->getID() > 0 ) {
+ if ( $archivedFile->userCan( File::DELETED_FILE ) ) {
+ $warnings['duplicate-archive'] = $archivedFile->getName();
} else {
$warnings['duplicate-archive'] = '';
}
}
- wfProfileOut( __METHOD__ );
-
return $warnings;
}
@@ -725,7 +702,7 @@ abstract class UploadBase {
* @return Status Indicating the whether the upload succeeded.
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
- wfProfileIn( __METHOD__ );
+ $this->getLocalFile()->load( File::READ_LATEST );
$status = $this->getLocalFile()->upload(
$this->mTempPath,
@@ -745,15 +722,44 @@ abstract class UploadBase {
WatchedItem::IGNORE_USER_RIGHTS
);
}
- wfRunHooks( 'UploadComplete', array( &$this ) );
- }
+ Hooks::run( 'UploadComplete', array( &$this ) );
- wfProfileOut( __METHOD__ );
+ $this->postProcessUpload();
+ }
return $status;
}
/**
+ * Perform extra steps after a successful upload.
+ *
+ * @since 1.25
+ */
+ public function postProcessUpload() {
+ global $wgUploadThumbnailRenderMap;
+
+ $jobs = array();
+
+ $sizes = $wgUploadThumbnailRenderMap;
+ rsort( $sizes );
+
+ $file = $this->getLocalFile();
+
+ foreach ( $sizes as $size ) {
+ if ( $file->isVectorized()
+ || $file->getWidth() > $size ) {
+ $jobs[] = new ThumbnailRenderJob( $file->getTitle(), array(
+ 'transformParams' => array( 'width' => $size ),
+ ) );
+ }
+ }
+
+ if ( $jobs ) {
+ JobQueueGroup::singleton()->push( $jobs );
+ }
+ }
+
+ /**
* Returns the title of the file to be uploaded. Sets mTitleError in case
* the name was illegal.
*
@@ -911,14 +917,11 @@ abstract class UploadBase {
*/
public function stashFile( User $user = null ) {
// was stashSessionFile
- wfProfileIn( __METHOD__ );
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $user );
$file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
$this->mLocalFile = $file;
- wfProfileOut( __METHOD__ );
-
return $file;
}
@@ -1058,7 +1061,6 @@ abstract class UploadBase {
*/
public static function detectScript( $file, $mime, $extension ) {
global $wgAllowTitlesInSVG;
- wfProfileIn( __METHOD__ );
# ugly hack: for text files, always look at the entire file.
# For binary field, just check the first K.
@@ -1074,7 +1076,6 @@ abstract class UploadBase {
$chunk = strtolower( $chunk );
if ( !$chunk ) {
- wfProfileOut( __METHOD__ );
return false;
}
@@ -1099,7 +1100,6 @@ abstract class UploadBase {
# check for HTML doctype
if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) {
- wfProfileOut( __METHOD__ );
return true;
}
@@ -1108,7 +1108,6 @@ abstract class UploadBase {
// PHP/expat will interpret the given encoding in the xml declaration (bug 47304)
if ( $extension == 'svg' || strpos( $mime, 'image/svg' ) === 0 ) {
if ( self::checkXMLEncodingMissmatch( $file ) ) {
- wfProfileOut( __METHOD__ );
return true;
}
@@ -1147,7 +1146,6 @@ abstract class UploadBase {
foreach ( $tags as $tag ) {
if ( false !== strpos( $chunk, $tag ) ) {
wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" );
- wfProfileOut( __METHOD__ );
return true;
}
@@ -1163,7 +1161,6 @@ abstract class UploadBase {
# look for script-types
if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
wfDebug( __METHOD__ . ": found script types\n" );
- wfProfileOut( __METHOD__ );
return true;
}
@@ -1171,7 +1168,6 @@ abstract class UploadBase {
# look for html-style script-urls
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;
}
@@ -1179,13 +1175,11 @@ abstract class UploadBase {
# look for css-style script-urls
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;
}
@@ -1252,9 +1246,10 @@ abstract class UploadBase {
/**
* @param string $filename
+ * @param bool $partial
* @return mixed False of the file is verified (does not contain scripts), array otherwise.
*/
- protected function detectScriptInSvg( $filename ) {
+ protected function detectScriptInSvg( $filename, $partial ) {
$this->mSVGNSError = false;
$check = new XmlTypeCheck(
$filename,
@@ -1264,7 +1259,8 @@ abstract class UploadBase {
);
if ( $check->wellFormed !== true ) {
// Invalid xml (bug 58553)
- return array( 'uploadinvalidxml' );
+ // But only when non-partial (bug 65724)
+ return $partial ? false : array( 'uploadinvalidxml' );
} elseif ( $check->filterMatch ) {
if ( $this->mSVGNSError ) {
return array( 'uploadscriptednamespace', $this->mSVGNSError );
@@ -1602,11 +1598,9 @@ abstract class UploadBase {
*/
public static function detectVirus( $file ) {
global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
- wfProfileIn( __METHOD__ );
if ( !$wgAntivirus ) {
wfDebug( __METHOD__ . ": virus scanner disabled\n" );
- wfProfileOut( __METHOD__ );
return null;
}
@@ -1615,7 +1609,6 @@ abstract class UploadBase {
wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus\n" );
$wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>",
array( 'virus-badscanner', $wgAntivirus ) );
- wfProfileOut( __METHOD__ );
return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus";
}
@@ -1689,8 +1682,6 @@ abstract class UploadBase {
wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output \n" );
}
- wfProfileOut( __METHOD__ );
-
return $output;
}
@@ -1705,6 +1696,7 @@ abstract class UploadBase {
private function checkOverwrite( $user ) {
// First check whether the local file can be overwritten
$file = $this->getLocalFile();
+ $file->load( File::READ_LATEST );
if ( $file->exists() ) {
if ( !self::userCanReUpload( $user, $file ) ) {
return array( 'fileexists-forbidden', $file->getName() );
@@ -1716,7 +1708,7 @@ abstract class UploadBase {
/* Check shared conflicts: if the local file does not exist, but
* wfFindFile finds a file, it exists in a shared repository.
*/
- $file = wfFindFile( $this->getTitle() );
+ $file = wfFindFile( $this->getTitle(), array( 'latest' => true ) );
if ( $file && !$user->isAllowed( 'reupload-shared' ) ) {
return array( 'fileexists-shared-forbidden', $file->getName() );
}
@@ -1745,6 +1737,8 @@ abstract class UploadBase {
return false;
}
+ $img->load( File::READ_LATEST );
+
return $user->getId() == $img->getUser( 'id' );
}
@@ -1950,29 +1944,38 @@ abstract class UploadBase {
}
/**
- * Get the current status of a chunked upload (used for polling).
- * The status will be read from the *current* user session.
+ * Get the current status of a chunked upload (used for polling)
+ *
+ * The value will be read from cache.
+ *
+ * @param User $user
* @param string $statusKey
* @return Status[]|bool
*/
- public static function getSessionStatus( $statusKey ) {
- return isset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] )
- ? $_SESSION[self::SESSION_STATUS_KEY][$statusKey]
- : false;
+ public static function getSessionStatus( User $user, $statusKey ) {
+ $key = wfMemcKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey );
+
+ return wfGetCache( CACHE_ANYTHING )->get( $key );
}
/**
- * Set the current status of a chunked upload (used for polling).
- * The status will be stored in the *current* user session.
+ * Set the current status of a chunked upload (used for polling)
+ *
+ * The value will be set in cache for 1 day
+ *
+ * @param User $user
* @param string $statusKey
* @param array|bool $value
* @return void
*/
- public static function setSessionStatus( $statusKey, $value ) {
+ public static function setSessionStatus( User $user, $statusKey, $value ) {
+ $key = wfMemcKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey );
+
+ $cache = wfGetCache( CACHE_ANYTHING );
if ( $value === false ) {
- unset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] );
+ $cache->delete( $key );
} else {
- $_SESSION[self::SESSION_STATUS_KEY][$statusKey] = $value;
+ $cache->set( $key, $value, 86400 );
}
}
}
diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php
index 14993023..cc9f5c85 100644
--- a/includes/upload/UploadFromChunks.php
+++ b/includes/upload/UploadFromChunks.php
@@ -77,7 +77,7 @@ class UploadFromChunks extends UploadFromFile {
$this->verifyChunk();
// Create a local stash target
- $this->mLocalFile = parent::stashFile();
+ $this->mLocalFile = parent::stashFile( $user );
// Update the initial file offset (based on file size)
$this->mOffset = $this->mLocalFile->getSize();
$this->mFileKey = $this->mLocalFile->getFileKey();
@@ -171,20 +171,6 @@ class UploadFromChunks extends UploadFromFile {
}
/**
- * Perform the upload, then remove the temp copy afterward
- * @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 int $index
* @return string
@@ -303,10 +289,10 @@ class UploadFromChunks extends UploadFromFile {
}
/**
- * Gets the current offset in fromt the stashedupload table
+ * Get the offset at which the next uploaded chunk will be appended to
* @return int Current byte offset of the chunk file set
*/
- private function getOffset() {
+ public function getOffset() {
if ( $this->mOffset !== null ) {
return $this->mOffset;
}
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index b6056401..fc59ace5 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -118,7 +118,7 @@ class UploadFromUrl extends UploadBase {
public static function isAllowedUrl( $url ) {
if ( !isset( self::$allowedUrls[$url] ) ) {
$allowed = true;
- wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) );
+ Hooks::run( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) );
self::$allowedUrls[$url] = $allowed;
}
@@ -231,12 +231,18 @@ class UploadFromUrl extends UploadBase {
* @return int Number of bytes handled
*/
public function saveTempFileChunk( $req, $buffer ) {
+ wfDebugLog( 'fileupload', 'Received chunk of ' . strlen( $buffer ) . ' bytes' );
$nbytes = fwrite( $this->mTmpHandle, $buffer );
if ( $nbytes == strlen( $buffer ) ) {
$this->mFileSize += $nbytes;
} else {
// Well... that's not good!
+ wfDebugLog(
+ 'fileupload',
+ 'Short write ' . $this->nbytes . '/' . strlen( $buffer ) .
+ ' bytes, aborting with ' . $this->mFileSize . ' uploaded so far'
+ );
fclose( $this->mTmpHandle );
$this->mTmpHandle = false;
}
@@ -262,6 +268,7 @@ class UploadFromUrl extends UploadBase {
if ( !$this->mTmpHandle ) {
return Status::newFatal( 'tmp-create-error' );
}
+ wfDebugLog( 'fileupload', 'Temporary file created "' . $this->mTempPath . '"' );
$this->mRemoveTempFile = true;
$this->mFileSize = 0;
@@ -275,7 +282,12 @@ class UploadFromUrl extends UploadBase {
if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) {
$options['timeout'] = $wgCopyUploadTimeout;
}
- $req = MWHttpRequest::factory( $this->mUrl, $options );
+ wfDebugLog(
+ 'fileupload',
+ 'Starting download from "' . $this->mUrl . '" ' .
+ '<' . implode( ',', array_keys( array_filter( $options ) ) ) . '>'
+ );
+ $req = MWHttpRequest::factory( $this->mUrl, $options, __METHOD__ );
$req->setCallback( array( $this, 'saveTempFileChunk' ) );
$status = $req->execute();
@@ -288,8 +300,14 @@ class UploadFromUrl extends UploadBase {
return Status::newFatal( 'tmp-write-error' );
}
- if ( !$status->isOk() ) {
- return $status;
+ wfDebugLog( 'fileupload', $status );
+ if ( $status->isOk() ) {
+ wfDebugLog( 'fileupload', 'Download by URL completed successfuly.' );
+ } else {
+ wfDebugLog(
+ 'fileupload',
+ 'Download by URL completed with HTTP status ' . $req->getStatus()
+ );
}
return $status;
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index 7d80b448..c07665a0 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -151,6 +151,7 @@ class UploadStash {
if ( !$this->files[$key]->exists() ) {
wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" );
+ // @todo Is this not an UploadStashFileNotFoundException case?
throw new UploadStashBadPathException( "path doesn't exist" );
}
@@ -726,9 +727,6 @@ class UploadStashFile extends UnregisteredLocalFile {
class UploadStashException extends MWException {
}
-class UploadStashNotAvailableException extends UploadStashException {
-}
-
class UploadStashFileNotFoundException extends UploadStashException {
}
diff --git a/includes/utils/AutoloadGenerator.php b/includes/utils/AutoloadGenerator.php
new file mode 100644
index 00000000..9cf8cab5
--- /dev/null
+++ b/includes/utils/AutoloadGenerator.php
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * Accepts a list of files and directories to search for
+ * php files and generates $wgAutoloadLocalClasses or $wgAutoloadClasses
+ * lines for all detected classes. These lines are written out
+ * to an autoload.php file in the projects provided basedir.
+ *
+ * Usage:
+ *
+ * $gen = new AutoloadGenerator( __DIR__ );
+ * $gen->readDir( __DIR__ . '/includes' );
+ * $gen->readFile( __DIR__ . '/foo.php' )
+ * $gen->generateAutoload();
+ */
+class AutoloadGenerator {
+ /**
+ * @var string Root path of the project being scanned for classes
+ */
+ protected $basepath;
+
+ /**
+ * @var ClassCollector Helper class extracts class names from php files
+ */
+ protected $collector;
+
+ /**
+ * @var array Map of file shortpath to list of FQCN detected within file
+ */
+ protected $classes = array();
+
+ /**
+ * @var string The global variable to write output to
+ */
+ protected $variableName = 'wgAutoloadClasses';
+
+ /**
+ * @var array Map of FQCN to relative path(from self::$basepath)
+ */
+ protected $overrides = array();
+
+ /**
+ * @param string $basepath Root path of the project being scanned for classes
+ * @param array|string $flags
+ *
+ * local - If this flag is set $wgAutoloadLocalClasses will be build instead
+ * of $wgAutoloadClasses
+ */
+ public function __construct( $basepath, $flags = array() ) {
+ if ( !is_array( $flags ) ) {
+ $flags = array( $flags );
+ }
+ $this->basepath = self::normalizePathSeparator( realpath( $basepath ) );
+ $this->collector = new ClassCollector;
+ if ( in_array( 'local', $flags ) ) {
+ $this->variableName = 'wgAutoloadLocalClasses';
+ }
+ }
+
+ /**
+ * Force a class to be autoloaded from a specific path, regardless of where
+ * or if it was detected.
+ *
+ * @param string $fqcn FQCN to force the location of
+ * @param string $inputPath Full path to the file containing the class
+ * @throws Exception
+ */
+ public function forceClassPath( $fqcn, $inputPath ) {
+ $path = self::normalizePathSeparator( realpath( $inputPath ) );
+ if ( !$path ) {
+ throw new \Exception( "Invalid path: $inputPath" );
+ }
+ $len = strlen( $this->basepath );
+ if ( substr( $path, 0, $len ) !== $this->basepath ) {
+ throw new \Exception( "Path is not within basepath: $inputPath" );
+ }
+ $shortpath = substr( $path, $len );
+ $this->overrides[$fqcn] = $shortpath;
+ }
+
+ /**
+ * @param string $inputPath Path to a php file to find classes within
+ * @throws Exception
+ */
+ public function readFile( $inputPath ) {
+ // NOTE: do NOT expand $inputPath using realpath(). It is perfectly
+ // reasonable for LocalSettings.php and similiar files to be symlinks
+ // to files that are outside of $this->basepath.
+ $inputPath = self::normalizePathSeparator( $inputPath );
+ $len = strlen( $this->basepath );
+ if ( substr( $inputPath, 0, $len ) !== $this->basepath ) {
+ throw new \Exception( "Path is not within basepath: $inputPath" );
+ }
+ $result = $this->collector->getClasses(
+ file_get_contents( $inputPath )
+ );
+ if ( $result ) {
+ $shortpath = substr( $inputPath, $len );
+ $this->classes[$shortpath] = $result;
+ }
+ }
+
+ /**
+ * @param string $dir Path to a directory to recursively search
+ * for php files with either .php or .inc extensions
+ */
+ public function readDir( $dir ) {
+ $it = new RecursiveDirectoryIterator(
+ self::normalizePathSeparator( realpath( $dir ) ) );
+ $it = new RecursiveIteratorIterator( $it );
+
+ foreach ( $it as $path => $file ) {
+ $ext = pathinfo( $path, PATHINFO_EXTENSION );
+ // some older files in mw use .inc
+ if ( $ext === 'php' || $ext === 'inc' ) {
+ $this->readFile( $path );
+ }
+ }
+ }
+
+ /**
+ * Write out all known classes to autoload.php in
+ * the provided basedir
+ *
+ * @param string $commandName Value used in file comment to direct
+ * developers towards the appropriate way to update the autoload.
+ */
+ public function generateAutoload( $commandName = 'AutoloadGenerator' ) {
+ $content = array();
+
+ // We need to generate a line each rather than exporting the
+ // full array so __DIR__ can be prepended to all the paths
+ $format = "%s => __DIR__ . %s,";
+ foreach ( $this->classes as $path => $contained ) {
+ $exportedPath = var_export( $path, true );
+ foreach ( $contained as $fqcn ) {
+ $content[$fqcn] = sprintf(
+ $format,
+ var_export( $fqcn, true ),
+ $exportedPath
+ );
+ }
+ }
+
+ foreach ( $this->overrides as $fqcn => $path ) {
+ $content[$fqcn] = sprintf(
+ $format,
+ var_export( $fqcn, true ),
+ var_export( $path, true )
+ );
+ }
+
+ // sort for stable output
+ ksort( $content );
+
+ // extensions using this generator are appending to the existing
+ // autoload.
+ if ( $this->variableName === 'wgAutoloadClasses' ) {
+ $op = '+=';
+ } else {
+ $op = '=';
+ }
+
+ $output = implode( "\n\t", $content );
+ file_put_contents(
+ $this->basepath . '/autoload.php',
+ <<<EOD
+<?php
+// This file is generated by $commandName, do not adjust manually
+// @codingStandardsIgnoreFile
+global \${$this->variableName};
+
+\${$this->variableName} {$op} array(
+ {$output}
+);
+
+EOD
+ );
+ }
+
+ /**
+ * Ensure that Unix-style path separators ("/") are used in the path.
+ *
+ * @param string $path
+ * @return string
+ */
+ protected static function normalizePathSeparator( $path ) {
+ return str_replace( '\\', '/', $path );
+ }
+}
+
+/**
+ * Reads PHP code and returns the FQCN of every class defined within it.
+ */
+class ClassCollector {
+
+ /**
+ * @var string Current namespace
+ */
+ protected $namespace = '';
+
+ /**
+ * @var array List of FQCN detected in this pass
+ */
+ protected $classes;
+
+ /**
+ * @var array Token from token_get_all() that started an expect sequence
+ */
+ protected $startToken;
+
+ /**
+ * @var array List of tokens that are members of the current expect sequence
+ */
+ protected $tokens;
+
+ /**
+ * @var string $code PHP code (including <?php) to detect class names from
+ * @return array List of FQCN detected within the tokens
+ */
+ public function getClasses( $code ) {
+ $this->namespace = '';
+ $this->classes = array();
+ $this->startToken = null;
+ $this->tokens = array();
+
+ foreach ( token_get_all( $code ) as $token ) {
+ if ( $this->startToken === null ) {
+ $this->tryBeginExpect( $token );
+ } else {
+ $this->tryEndExpect( $token );
+ }
+ }
+
+ return $this->classes;
+ }
+
+ /**
+ * Determine if $token begins the next expect sequence.
+ *
+ * @param array $token
+ */
+ protected function tryBeginExpect( $token ) {
+ if ( is_string( $token ) ) {
+ return;
+ }
+ switch ( $token[0] ) {
+ case T_NAMESPACE:
+ case T_CLASS:
+ case T_INTERFACE:
+ $this->startToken = $token;
+ }
+ }
+
+ /**
+ * Accepts the next token in an expect sequence
+ *
+ * @param array
+ */
+ protected function tryEndExpect( $token ) {
+ switch ( $this->startToken[0] ) {
+ case T_NAMESPACE:
+ if ( $token === ';' || $token === '{' ) {
+ $this->namespace = $this->implodeTokens() . '\\';
+ } else {
+ $this->tokens[] = $token;
+ }
+ break;
+
+ case T_CLASS:
+ case T_INTERFACE:
+ $this->tokens[] = $token;
+ if ( is_array( $token ) && $token[0] === T_STRING ) {
+ $this->classes[] = $this->namespace . $this->implodeTokens();
+ }
+ }
+ }
+
+ /**
+ * Returns the string representation of the tokens within the
+ * current expect sequence and resets the sequence.
+ *
+ * @return string
+ */
+ protected function implodeTokens() {
+ $content = array();
+ foreach ( $this->tokens as $token ) {
+ $content[] = is_string( $token ) ? $token : $token[1];
+ }
+
+ $this->tokens = array();
+ $this->startToken = null;
+
+ return trim( implode( '', $content ), " \n\t" );
+ }
+}
diff --git a/includes/utils/Cdb.php b/includes/utils/Cdb.php
deleted file mode 100644
index 3ceb620f..00000000
--- a/includes/utils/Cdb.php
+++ /dev/null
@@ -1,163 +0,0 @@
-<?php
-/**
- * Native CDB file reader and 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
- */
-
-/**
- * Read from a CDB file.
- * Native and pure PHP implementations are provided.
- * http://cr.yp.to/cdb.html
- */
-abstract class CdbReader {
- /**
- * The file handle
- */
- protected $handle;
-
- /**
- * Open a file and return a subclass instance
- *
- * @param string $fileName
- *
- * @return CdbReader
- */
- public static function open( $fileName ) {
- return self::haveExtension() ?
- new CdbReaderDBA( $fileName ) :
- new CdbReaderPHP( $fileName );
- }
-
- /**
- * Returns true if the native extension is available
- *
- * @return bool
- */
- public static function haveExtension() {
- if ( !function_exists( 'dba_handlers' ) ) {
- return false;
- }
- $handlers = dba_handlers();
- if ( !in_array( 'cdb', $handlers ) || !in_array( 'cdb_make', $handlers ) ) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Create the object and open the file
- *
- * @param string $fileName
- */
- abstract public function __construct( $fileName );
-
- /**
- * Close the file. Optional, you can just let the variable go out of scope.
- */
- abstract public function close();
-
- /**
- * Get a value with a given key. Only string values are supported.
- *
- * @param string $key
- */
- abstract public function get( $key );
-}
-
-/**
- * Write to a CDB file.
- * Native and pure PHP implementations are provided.
- */
-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 string $fileName
- *
- * @return CdbWriterDBA|CdbWriterPHP
- */
- public static function open( $fileName ) {
- return CdbReader::haveExtension() ?
- new CdbWriterDBA( $fileName ) :
- new CdbWriterPHP( $fileName );
- }
-
- /**
- * Create the object and open the file
- *
- * @param string $fileName
- */
- abstract public function __construct( $fileName );
-
- /**
- * Set a key to a given value. The value will be converted to string.
- * @param string $key
- * @param string $value
- */
- abstract public function set( $key, $value );
-
- /**
- * Close the writer object. You should call this function before the object
- * goes out of scope, to write out the final hashtables.
- */
- abstract public function close();
-
- /**
- * If the object goes out of scope, close it for sanity
- */
- public function __destruct() {
- if ( isset( $this->handle ) ) {
- $this->close();
- }
- }
-
- /**
- * Are we running on Windows?
- * @return bool
- */
- protected function isWindows() {
- return substr( php_uname(), 0, 7 ) == 'Windows';
- }
-}
-
-/**
- * Exception for Cdb errors.
- * This explicitly doesn't subclass MWException to encourage reuse.
- */
-class CdbException extends Exception {
-}
diff --git a/includes/utils/CdbDBA.php b/includes/utils/CdbDBA.php
deleted file mode 100644
index efcaf21f..00000000
--- a/includes/utils/CdbDBA.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?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/utils/CdbPHP.php b/includes/utils/CdbPHP.php
deleted file mode 100644
index 19d747a7..00000000
--- a/includes/utils/CdbPHP.php
+++ /dev/null
@@ -1,494 +0,0 @@
-<?php
-/**
- * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
- * appears in PHP 5.3. Changes are:
- * * Error returns replaced with exceptions
- * * Exception thrown if sizes or offsets are between 2GB and 4GB
- * * Some variables renamed
- *
- * This 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
- */
-
-/**
- * Common functions for readers and writers
- */
-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 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;
- }
- }
-
- /**
- * Shift a signed integer right as if it were unsigned
- * @param int $a
- * @param int $b
- * @return int
- */
- public static function unsignedShiftRight( $a, $b ) {
- if ( $b == 0 ) {
- return $a;
- }
- if ( $a & 0x80000000 ) {
- return ( ( $a & 0x7fffffff ) >> $b ) | ( 0x40000000 >> ( $b - 1 ) );
- } else {
- return $a >> $b;
- }
- }
-
- /**
- * The CDB hash function.
- *
- * @param string $s
- *
- * @return int
- */
- public static function hash( $s ) {
- $h = 5381;
- $len = strlen( $s );
- for ( $i = 0; $i < $len; $i++ ) {
- $h5 = ( $h << 5 ) & 0xffffffff;
- // Do a 32-bit sum
- // Inlined here for speed
- $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff );
- $h =
- (
- ( $sum & 0x40000000 ? 1 : 0 )
- + ( $h & 0x80000000 ? 2 : 0 )
- + ( $h & 0x40000000 ? 1 : 0 )
- + ( $h5 & 0x80000000 ? 2 : 0 )
- + ( $h5 & 0x40000000 ? 1 : 0 )
- ) << 30
- | ( $sum & 0x3fffffff );
- $h ^= ord( $s[$i] );
- $h &= 0xffffffff;
- }
-
- return $h;
- }
-}
-
-/**
- * CDB reader class
- */
-class CdbReaderPHP extends CdbReader {
- /** The filename */
- protected $fileName;
-
- /* number of hash slots searched under this key */
- protected $loop;
-
- /* initialized if loop is nonzero */
- protected $khash;
-
- /* initialized if loop is nonzero */
- protected $kpos;
-
- /* initialized if loop is nonzero */
- protected $hpos;
-
- /* initialized if loop is nonzero */
- protected $hslots;
-
- /* initialized if findNext() returns true */
- protected $dpos;
-
- /* initialized if cdb_findnext() returns 1 */
- protected $dlen;
-
- /**
- * @param string $fileName
- * @throws CdbException
- */
- public function __construct( $fileName ) {
- $this->fileName = $fileName;
- $this->handle = fopen( $fileName, 'rb' );
- if ( !$this->handle ) {
- throw new CdbException( 'Unable to open CDB file "' . $this->fileName . '".' );
- }
- $this->findStart();
- }
-
- public function close() {
- if ( isset( $this->handle ) ) {
- fclose( $this->handle );
- }
- unset( $this->handle );
- }
-
- /**
- * @param mixed $key
- * @return bool|string
- */
- public function get( $key ) {
- // strval is required
- if ( $this->find( strval( $key ) ) ) {
- return $this->read( $this->dlen, $this->dpos );
- } else {
- return false;
- }
- }
-
- /**
- * @param string $key
- * @param int $pos
- * @return bool
- */
- protected function match( $key, $pos ) {
- $buf = $this->read( strlen( $key ), $pos );
-
- return $buf === $key;
- }
-
- protected function findStart() {
- $this->loop = 0;
- }
-
- /**
- * @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 CdbException(
- 'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
- }
-
- if ( $length == 0 ) {
- return '';
- }
-
- $buf = fread( $this->handle, $length );
- if ( $buf === false || strlen( $buf ) !== $length ) {
- 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 string $s
- * @throws CdbException
- * @return mixed
- */
- protected function unpack31( $s ) {
- $data = unpack( 'V', $s );
- if ( $data[1] > 0x7fffffff ) {
- throw new CdbException(
- 'Error in CDB file "' . $this->fileName . '", integer too big.' );
- }
-
- return $data[1];
- }
-
- /**
- * Unpack a 32-bit signed integer
- * @param string $s
- * @return int
- */
- protected function unpackSigned( $s ) {
- $data = unpack( 'va/vb', $s );
-
- return $data['a'] | ( $data['b'] << 16 );
- }
-
- /**
- * @param string $key
- * @return bool
- */
- protected function findNext( $key ) {
- if ( !$this->loop ) {
- $u = CdbFunctions::hash( $key );
- $buf = $this->read( 8, ( $u << 3 ) & 2047 );
- $this->hslots = $this->unpack31( substr( $buf, 4 ) );
- if ( !$this->hslots ) {
- return false;
- }
- $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) );
- $this->khash = $u;
- $u = CdbFunctions::unsignedShiftRight( $u, 8 );
- $u = CdbFunctions::unsignedMod( $u, $this->hslots );
- $u <<= 3;
- $this->kpos = $this->hpos + $u;
- }
-
- while ( $this->loop < $this->hslots ) {
- $buf = $this->read( 8, $this->kpos );
- $pos = $this->unpack31( substr( $buf, 4 ) );
- if ( !$pos ) {
- return false;
- }
- $this->loop += 1;
- $this->kpos += 8;
- if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) {
- $this->kpos = $this->hpos;
- }
- $u = $this->unpackSigned( substr( $buf, 0, 4 ) );
- if ( $u === $this->khash ) {
- $buf = $this->read( 8, $pos );
- $keyLen = $this->unpack31( substr( $buf, 0, 4 ) );
- if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) {
- // Found
- $this->dlen = $this->unpack31( substr( $buf, 4 ) );
- $this->dpos = $pos + 8 + $keyLen;
-
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * @param mixed $key
- * @return bool
- */
- protected function find( $key ) {
- $this->findStart();
-
- return $this->findNext( $key );
- }
-}
-
-/**
- * CDB writer class
- */
-class CdbWriterPHP extends CdbWriter {
- protected $hplist;
-
- protected $numentries;
-
- protected $pos;
-
- /**
- * @param string $fileName
- */
- public function __construct( $fileName ) {
- $this->realFileName = $fileName;
- $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
- $this->handle = fopen( $this->tmpFileName, 'wb' );
- if ( !$this->handle ) {
- $this->throwException(
- 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' );
- }
- $this->hplist = array();
- $this->numentries = 0;
- $this->pos = 2048; // leaving space for the pointer array, 256 * 8
- if ( fseek( $this->handle, $this->pos ) == -1 ) {
- $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' );
- }
- }
-
- /**
- * @param string $key
- * @param string $value
- */
- public function set( $key, $value ) {
- if ( strval( $key ) === '' ) {
- // DBA cross-check hack
- return;
- }
- $this->addbegin( strlen( $key ), strlen( $value ) );
- $this->write( $key );
- $this->write( $value );
- $this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) );
- }
-
- /**
- * @throws CdbException
- */
- public function close() {
- $this->finish();
- if ( isset( $this->handle ) ) {
- fclose( $this->handle );
- }
- if ( $this->isWindows() && file_exists( $this->realFileName ) ) {
- unlink( $this->realFileName );
- }
- if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
- $this->throwException( 'Unable to move the new CDB file into place.' );
- }
- unset( $this->handle );
- }
-
- /**
- * @throws CdbException
- * @param string $buf
- */
- protected function write( $buf ) {
- $len = fwrite( $this->handle, $buf );
- if ( $len !== strlen( $buf ) ) {
- $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' );
- }
- }
-
- /**
- * @throws CdbException
- * @param int $len
- */
- protected function posplus( $len ) {
- $newpos = $this->pos + $len;
- if ( $newpos > 0x7fffffff ) {
- $this->throwException(
- 'A value in the CDB file "' . $this->tmpFileName . '" is too large.' );
- }
- $this->pos = $newpos;
- }
-
- /**
- * @param int $keylen
- * @param int $datalen
- * @param int $h
- */
- protected function addend( $keylen, $datalen, $h ) {
- $this->hplist[] = array(
- 'h' => $h,
- 'p' => $this->pos
- );
-
- $this->numentries++;
- $this->posplus( 8 );
- $this->posplus( $keylen );
- $this->posplus( $datalen );
- }
-
- /**
- * @throws CdbException
- * @param int $keylen
- * @param int $datalen
- */
- protected function addbegin( $keylen, $datalen ) {
- if ( $keylen > 0x7fffffff ) {
- $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' );
- }
- if ( $datalen > 0x7fffffff ) {
- $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' );
- }
- $buf = pack( 'VV', $keylen, $datalen );
- $this->write( $buf );
- }
-
- /**
- * @throws CdbException
- */
- protected function finish() {
- // Hack for DBA cross-check
- $this->hplist = array_reverse( $this->hplist );
-
- // 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']];
- }
-
- // Fill in $starts with the *end* indexes
- $starts = array();
- $pos = 0;
- for ( $i = 0; $i < 256; ++$i ) {
- $pos += $counts[$i];
- $starts[$i] = $pos;
- }
-
- // Excessively clever and indulgent code to simultaneously fill $packedTables
- // with the packed hashtables, and adjust the elements of $starts
- // to actually point to the starts instead of the ends.
- $packedTables = array_fill( 0, $this->numentries, false );
- foreach ( $this->hplist as $item ) {
- $packedTables[--$starts[255 & $item['h']]] = $item;
- }
-
- $final = '';
- for ( $i = 0; $i < 256; ++$i ) {
- $count = $counts[$i];
-
- // The size of the hashtable will be double the item count.
- // The rest of the slots will be empty.
- $len = $count + $count;
- $final .= pack( 'VV', $this->pos, $len );
-
- $hashtable = array();
- for ( $u = 0; $u < $len; ++$u ) {
- $hashtable[$u] = array( 'h' => 0, 'p' => 0 );
- }
-
- // Fill the hashtable, using the next empty slot if the hashed slot
- // is taken.
- for ( $u = 0; $u < $count; ++$u ) {
- $hp = $packedTables[$starts[$i] + $u];
- $where = CdbFunctions::unsignedMod(
- CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len );
- while ( $hashtable[$where]['p'] ) {
- if ( ++$where == $len ) {
- $where = 0;
- }
- }
- $hashtable[$where] = $hp;
- }
-
- // Write the hashtable
- for ( $u = 0; $u < $len; ++$u ) {
- $buf = pack( 'vvV',
- $hashtable[$u]['h'] & 0xffff,
- CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ),
- $hashtable[$u]['p'] );
- $this->write( $buf );
- $this->posplus( 8 );
- }
- }
-
- // Write the pointer array at the start of the file
- rewind( $this->handle );
- if ( ftell( $this->handle ) != 0 ) {
- $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' );
- }
- $this->write( $final );
- }
-
- /**
- * Clean up the temp file and throw an exception
- *
- * @param string $msg
- * @throws CdbException
- */
- protected function throwException( $msg ) {
- if ( $this->handle ) {
- fclose( $this->handle );
- unlink( $this->tmpFileName );
- }
- throw new CdbException( $msg );
- }
-}
diff --git a/includes/utils/IP.php b/includes/utils/IP.php
index 0e2db8cc..4441236d 100644
--- a/includes/utils/IP.php
+++ b/includes/utils/IP.php
@@ -629,6 +629,25 @@ class IP {
}
/**
+ * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
+ *
+ * @since 1.25
+ *
+ * @param string $ip the IP to check
+ * @param array $ranges the IP ranges, each element a range
+ *
+ * @return bool true if the specified adress belongs to the specified range; otherwise, false.
+ */
+ public static function isInRanges( $ip, $ranges ) {
+ foreach ( $ranges as $range ) {
+ if ( self::isInRange( $ip, $range ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Convert some unusual representations of IPv4 addresses to their
* canonical dotted quad representation.
*
@@ -698,7 +717,7 @@ class IP {
*/
public static function isTrustedProxy( $ip ) {
$trusted = self::isConfiguredProxy( $ip );
- wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
+ Hooks::run( 'IsTrustedProxy', array( &$ip, &$trusted ) );
return $trusted;
}
@@ -712,7 +731,6 @@ class IP {
public static function isConfiguredProxy( $ip ) {
global $wgSquidServers, $wgSquidServersNoPurge;
- wfProfileIn( __METHOD__ );
// Quick check of known singular proxy servers
$trusted = in_array( $ip, $wgSquidServers );
@@ -723,7 +741,6 @@ class IP {
}
$trusted = self::$proxyIpSet->match( $ip );
}
- wfProfileOut( __METHOD__ );
return $trusted;
}
diff --git a/includes/utils/MWCryptHKDF.php b/includes/utils/MWCryptHKDF.php
index cc136793..950dd846 100644
--- a/includes/utils/MWCryptHKDF.php
+++ b/includes/utils/MWCryptHKDF.php
@@ -103,6 +103,7 @@ class MWCryptHKDF {
* @param string $algorithm Name of hashing algorithm
* @param BagOStuff $cache
* @param string|array $context Context to mix into HKDF context
+ * @throws MWException
*/
public function __construct( $secretKeyMaterial, $algorithm, $cache, $context ) {
if ( strlen( $secretKeyMaterial ) < 16 ) {
@@ -157,6 +158,7 @@ class MWCryptHKDF {
/**
* Return a singleton instance, based on the global configs.
* @return HKDF
+ * @throws MWException
*/
protected static function singleton() {
global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey;
@@ -271,14 +273,15 @@ class MWCryptHKDF {
*
* @param string $hash Hashing Algorithm
* @param string $prk A pseudorandom key of at least HashLen octets
- * (usually, the output from the extract step)
+ * (usually, the output from the extract step)
* @param string $info Optional context and application specific information
- * (can be a zero-length string)
+ * (can be a zero-length string)
* @param int $bytes Length of output keying material in bytes
- * (<= 255*HashLen)
+ * (<= 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.
+ * In MediaWiki, this is used to seed future Extractions.
* @return string Cryptographically secure random string $bytes long
+ * @throws MWException
*/
private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) {
$hashLen = MWCryptHKDF::$hashLength[$hash];
diff --git a/includes/utils/MWCryptRand.php b/includes/utils/MWCryptRand.php
index b602f78e..e6c0e784 100644
--- a/includes/utils/MWCryptRand.php
+++ b/includes/utils/MWCryptRand.php
@@ -294,7 +294,6 @@ class MWCryptRand {
* @see self::generate()
*/
public function realGenerate( $bytes, $forceStrong = false ) {
- wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " .
wfGetAllCallers( 5 ) . "\n" );
@@ -314,7 +313,6 @@ class MWCryptRand {
// entropy so this is also preferable to just trying to read urandom because it may work
// on Windows systems as well.
if ( function_exists( 'mcrypt_create_iv' ) ) {
- wfProfileIn( __METHOD__ . '-mcrypt' );
$rem = $bytes - strlen( $buffer );
$iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
if ( $iv === false ) {
@@ -324,7 +322,6 @@ class MWCryptRand {
wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) .
" bytes of randomness.\n" );
}
- wfProfileOut( __METHOD__ . '-mcrypt' );
}
}
@@ -337,7 +334,6 @@ class MWCryptRand {
if ( function_exists( 'openssl_random_pseudo_bytes' )
&& ( !wfIsWindows() || version_compare( PHP_VERSION, '5.3.4', '>=' ) )
) {
- wfProfileIn( __METHOD__ . '-openssl' );
$rem = $bytes - strlen( $buffer );
$openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
if ( $openssl_bytes === false ) {
@@ -353,7 +349,6 @@ class MWCryptRand {
// using it use it's say on whether the randomness is strong
$this->strong = !!$openssl_strong;
}
- wfProfileOut( __METHOD__ . '-openssl' );
}
}
@@ -361,7 +356,6 @@ class MWCryptRand {
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 " .
@@ -400,7 +394,6 @@ class MWCryptRand {
} else {
wfDebug( __METHOD__ . ": /dev/urandom could not be opened.\n" );
}
- wfProfileOut( __METHOD__ . '-fopen-urandom' );
}
// If we cannot use or generate enough data from a secure source
@@ -414,12 +407,10 @@ class MWCryptRand {
": Falling back to using a pseudo random state to generate randomness.\n" );
}
while ( strlen( $buffer ) < $bytes ) {
- wfProfileIn( __METHOD__ . '-fallback' );
$buffer .= $this->hmac( $this->randomState(), mt_rand() );
// This code is never really cryptographically strong, if we use it
// at all, then set strong to false.
$this->strong = false;
- wfProfileOut( __METHOD__ . '-fallback' );
}
// Once the buffer has been filled up with enough random data to fulfill
@@ -431,8 +422,6 @@ class MWCryptRand {
wfDebug( __METHOD__ . ": " . strlen( $buffer ) .
" bytes of randomness leftover in the buffer.\n" );
- wfProfileOut( __METHOD__ );
-
return $generated;
}
diff --git a/includes/utils/MWFunction.php b/includes/utils/MWFunction.php
index 3a0492dc..fa7eebe8 100644
--- a/includes/utils/MWFunction.php
+++ b/includes/utils/MWFunction.php
@@ -23,41 +23,18 @@
class MWFunction {
/**
- * @deprecated since 1.22; use call_user_func()
- * @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 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 string $class
* @param array $args
* @return object
+ * @deprecated 1.25 Use ObjectFactory::getObjectFromSpec() instead
*/
public static function newObj( $class, $args = array() ) {
- if ( !count( $args ) ) {
- return new $class;
- }
-
- $ref = new ReflectionClass( $class );
+ wfDeprecated( __METHOD__, '1.25' );
- return $ref->newInstanceArgs( $args );
+ return ObjectFactory::getObjectFromSpec( array(
+ 'class' => $class,
+ 'args' => $args,
+ 'closure_expansion' => false,
+ ) );
}
}
diff --git a/includes/utils/UIDGenerator.php b/includes/utils/UIDGenerator.php
index 5346afa6..92415877 100644
--- a/includes/utils/UIDGenerator.php
+++ b/includes/utils/UIDGenerator.php
@@ -119,6 +119,7 @@ class UIDGenerator {
/**
* @param array $info (UIDGenerator::millitime(), counter, clock sequence)
* @return string 88 bits
+ * @throws MWException
*/
protected function getTimestampedID88( array $info ) {
list( $time, $counter ) = $info;
@@ -163,6 +164,7 @@ class UIDGenerator {
/**
* @param array $info (UIDGenerator::millitime(), counter, clock sequence)
* @return string 128 bits
+ * @throws MWException
*/
protected function getTimestampedID128( array $info ) {
list( $time, $counter, $clkSeq ) = $info;
@@ -260,6 +262,7 @@ class UIDGenerator {
* @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
+ * @throws MWException
*/
protected function getSequentialPerNodeIDs( $bucket, $bits, $count, $flags ) {
if ( $count <= 0 ) {
@@ -278,7 +281,7 @@ class UIDGenerator {
if ( ( $flags & self::QUICK_VOLATILE ) && PHP_SAPI !== 'cli' ) {
try {
$cache = ObjectCache::newAccelerator( array() );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
// not supported
}
}
@@ -436,6 +439,7 @@ class UIDGenerator {
/**
* @param array $time Result of UIDGenerator::millitime()
* @return string 46 MSBs of "milliseconds since epoch" in binary (rolls over in 4201)
+ * @throws MWException
*/
protected function millisecondsSinceEpochBinary( array $time ) {
list( $sec, $msec ) = $time;
diff --git a/includes/utils/ZipDirectoryReader.php b/includes/utils/ZipDirectoryReader.php
index bc849766..86960aa1 100644
--- a/includes/utils/ZipDirectoryReader.php
+++ b/includes/utils/ZipDirectoryReader.php
@@ -186,6 +186,7 @@ class ZipDirectoryReader {
* Throw an error, and log a debug message
* @param mixed $code
* @param string $debugMessage
+ * @throws ZipDirectoryReaderError
*/
function error( $code, $debugMessage ) {
wfDebug( __CLASS__ . ": Fatal error: $debugMessage\n" );